All Files (52.53% covered at 10.39 hits/line)
197 files in total.
13319 relevant lines.
6997 lines covered and
6322 lines missed
-
# encoding:utf-8
-
#--
-
# Copyright (C) 2006-2015 Bob Aman
-
#
-
# Licensed under the Apache License, Version 2.0 (the "License");
-
# you may not use this file except in compliance with the License.
-
# You may obtain a copy of the License at
-
#
-
# http://www.apache.org/licenses/LICENSE-2.0
-
#
-
# Unless required by applicable law or agreed to in writing, software
-
# distributed under the License is distributed on an "AS IS" BASIS,
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-
# See the License for the specific language governing permissions and
-
# limitations under the License.
-
#++
-
-
-
1
begin
-
1
require "addressable/idna/native"
-
rescue LoadError
-
# libidn or the idn gem was not available, fall back on a pure-Ruby
-
# implementation...
-
1
require "addressable/idna/pure"
-
end
-
# encoding:utf-8
-
#--
-
# Copyright (C) 2006-2015 Bob Aman
-
#
-
# Licensed under the Apache License, Version 2.0 (the "License");
-
# you may not use this file except in compliance with the License.
-
# You may obtain a copy of the License at
-
#
-
# http://www.apache.org/licenses/LICENSE-2.0
-
#
-
# Unless required by applicable law or agreed to in writing, software
-
# distributed under the License is distributed on an "AS IS" BASIS,
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-
# See the License for the specific language governing permissions and
-
# limitations under the License.
-
#++
-
-
-
1
require "idn"
-
-
module Addressable
-
module IDNA
-
def self.punycode_encode(value)
-
IDN::Punycode.encode(value.to_s)
-
end
-
-
def self.punycode_decode(value)
-
IDN::Punycode.decode(value.to_s)
-
end
-
-
def self.unicode_normalize_kc(value)
-
IDN::Stringprep.nfkc_normalize(value.to_s)
-
end
-
-
def self.to_ascii(value)
-
value.to_s.split('.', -1).map do |segment|
-
if segment.size > 0
-
IDN::Idna.toASCII(segment)
-
else
-
''
-
end
-
end.join('.')
-
end
-
-
def self.to_unicode(value)
-
value.to_s.split('.', -1).map do |segment|
-
if segment.size > 0
-
IDN::Idna.toUnicode(segment)
-
else
-
''
-
end
-
end.join('.')
-
end
-
end
-
end
-
# encoding:utf-8
-
#--
-
# Copyright (C) 2006-2015 Bob Aman
-
#
-
# Licensed under the Apache License, Version 2.0 (the "License");
-
# you may not use this file except in compliance with the License.
-
# You may obtain a copy of the License at
-
#
-
# http://www.apache.org/licenses/LICENSE-2.0
-
#
-
# Unless required by applicable law or agreed to in writing, software
-
# distributed under the License is distributed on an "AS IS" BASIS,
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-
# See the License for the specific language governing permissions and
-
# limitations under the License.
-
#++
-
-
-
1
module Addressable
-
1
module IDNA
-
# This module is loosely based on idn_actionmailer by Mick Staugaard,
-
# the unicode library by Yoshida Masato, and the punycode implementation
-
# by Kazuhiro Nishiyama. Most of the code was copied verbatim, but
-
# some reformatting was done, and some translation from C was done.
-
#
-
# Without their code to work from as a base, we'd all still be relying
-
# on the presence of libidn. Which nobody ever seems to have installed.
-
#
-
# Original sources:
-
# http://github.com/staugaard/idn_actionmailer
-
# http://www.yoshidam.net/Ruby.html#unicode
-
# http://rubyforge.org/frs/?group_id=2550
-
-
-
1
UNICODE_TABLE = File.expand_path(
-
File.join(File.dirname(__FILE__), '../../..', 'data/unicode.data')
-
)
-
-
1
ACE_PREFIX = "xn--"
-
-
1
UTF8_REGEX = /\A(?:
-
[\x09\x0A\x0D\x20-\x7E] # ASCII
-
| [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
-
| \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
-
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
-
| \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
-
| \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
-
| [\xF1-\xF3][\x80-\xBF]{3} # planes 4nil5
-
| \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
-
)*\z/mnx
-
-
1
UTF8_REGEX_MULTIBYTE = /(?:
-
[\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
-
| \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
-
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
-
| \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
-
| \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
-
| [\xF1-\xF3][\x80-\xBF]{3} # planes 4nil5
-
| \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
-
)/mnx
-
-
# :startdoc:
-
-
# Converts from a Unicode internationalized domain name to an ASCII
-
# domain name as described in RFC 3490.
-
1
def self.to_ascii(input)
-
16
input = input.to_s unless input.is_a?(String)
-
16
input = input.dup
-
16
if input.respond_to?(:force_encoding)
-
16
input.force_encoding(Encoding::ASCII_8BIT)
-
end
-
16
if input =~ UTF8_REGEX && input =~ UTF8_REGEX_MULTIBYTE
-
parts = unicode_downcase(input).split('.')
-
parts.map! do |part|
-
if part.respond_to?(:force_encoding)
-
part.force_encoding(Encoding::ASCII_8BIT)
-
end
-
if part =~ UTF8_REGEX && part =~ UTF8_REGEX_MULTIBYTE
-
ACE_PREFIX + punycode_encode(unicode_normalize_kc(part))
-
else
-
part
-
end
-
end
-
parts.join('.')
-
else
-
16
input
-
end
-
end
-
-
# Converts from an ASCII domain name to a Unicode internationalized
-
# domain name as described in RFC 3490.
-
1
def self.to_unicode(input)
-
input = input.to_s unless input.is_a?(String)
-
parts = input.split('.')
-
parts.map! do |part|
-
if part =~ /^#{ACE_PREFIX}(.+)/
-
punycode_decode(part[/^#{ACE_PREFIX}(.+)/, 1])
-
else
-
part
-
end
-
end
-
output = parts.join('.')
-
if output.respond_to?(:force_encoding)
-
output.force_encoding(Encoding::UTF_8)
-
end
-
output
-
end
-
-
# Unicode normalization form KC.
-
1
def self.unicode_normalize_kc(input)
-
164
input = input.to_s unless input.is_a?(String)
-
164
unpacked = input.unpack("U*")
-
164
unpacked =
-
unicode_compose(unicode_sort_canonical(unicode_decompose(unpacked)))
-
164
return unpacked.pack("U*")
-
end
-
-
##
-
# Unicode aware downcase method.
-
#
-
# @api private
-
# @param [String] input
-
# The input string.
-
# @return [String] The downcased result.
-
1
def self.unicode_downcase(input)
-
input = input.to_s unless input.is_a?(String)
-
unpacked = input.unpack("U*")
-
unpacked.map! { |codepoint| lookup_unicode_lowercase(codepoint) }
-
return unpacked.pack("U*")
-
end
-
2
(class <<self; private :unicode_downcase; end)
-
-
1
def self.unicode_compose(unpacked)
-
164
unpacked_result = []
-
164
length = unpacked.length
-
-
164
return unpacked if length == 0
-
-
148
starter = unpacked[0]
-
148
starter_cc = lookup_unicode_combining_class(starter)
-
148
starter_cc = 256 if starter_cc != 0
-
148
for i in 1...length
-
1992
ch = unpacked[i]
-
1992
cc = lookup_unicode_combining_class(ch)
-
-
if (starter_cc == 0 &&
-
1992
(composite = unicode_compose_pair(starter, ch)) != nil)
-
starter = composite
-
startercc = lookup_unicode_combining_class(composite)
-
else
-
1992
unpacked_result << starter
-
1992
starter = ch
-
1992
startercc = cc
-
end
-
end
-
148
unpacked_result << starter
-
148
return unpacked_result
-
end
-
2
(class <<self; private :unicode_compose; end)
-
-
1
def self.unicode_compose_pair(ch_one, ch_two)
-
1992
if ch_one >= HANGUL_LBASE && ch_one < HANGUL_LBASE + HANGUL_LCOUNT &&
-
ch_two >= HANGUL_VBASE && ch_two < HANGUL_VBASE + HANGUL_VCOUNT
-
# Hangul L + V
-
return HANGUL_SBASE + (
-
(ch_one - HANGUL_LBASE) * HANGUL_VCOUNT + (ch_two - HANGUL_VBASE)
-
) * HANGUL_TCOUNT
-
elsif ch_one >= HANGUL_SBASE &&
-
ch_one < HANGUL_SBASE + HANGUL_SCOUNT &&
-
(ch_one - HANGUL_SBASE) % HANGUL_TCOUNT == 0 &&
-
ch_two >= HANGUL_TBASE && ch_two < HANGUL_TBASE + HANGUL_TCOUNT
-
# Hangul LV + T
-
return ch_one + (ch_two - HANGUL_TBASE)
-
end
-
-
1992
p = []
-
1992
ucs4_to_utf8 = lambda do |ch|
-
3984
if ch < 128
-
3984
p << ch
-
elsif ch < 2048
-
p << (ch >> 6 | 192)
-
p << (ch & 63 | 128)
-
elsif ch < 0x10000
-
p << (ch >> 12 | 224)
-
p << (ch >> 6 & 63 | 128)
-
p << (ch & 63 | 128)
-
elsif ch < 0x200000
-
p << (ch >> 18 | 240)
-
p << (ch >> 12 & 63 | 128)
-
p << (ch >> 6 & 63 | 128)
-
p << (ch & 63 | 128)
-
elsif ch < 0x4000000
-
p << (ch >> 24 | 248)
-
p << (ch >> 18 & 63 | 128)
-
p << (ch >> 12 & 63 | 128)
-
p << (ch >> 6 & 63 | 128)
-
p << (ch & 63 | 128)
-
elsif ch < 0x80000000
-
p << (ch >> 30 | 252)
-
p << (ch >> 24 & 63 | 128)
-
p << (ch >> 18 & 63 | 128)
-
p << (ch >> 12 & 63 | 128)
-
p << (ch >> 6 & 63 | 128)
-
p << (ch & 63 | 128)
-
end
-
end
-
-
1992
ucs4_to_utf8.call(ch_one)
-
1992
ucs4_to_utf8.call(ch_two)
-
-
1992
return lookup_unicode_composition(p)
-
end
-
2
(class <<self; private :unicode_compose_pair; end)
-
-
1
def self.unicode_sort_canonical(unpacked)
-
164
unpacked = unpacked.dup
-
164
i = 1
-
164
length = unpacked.length
-
-
164
return unpacked if length < 2
-
-
148
while i < length
-
1992
last = unpacked[i-1]
-
1992
ch = unpacked[i]
-
1992
last_cc = lookup_unicode_combining_class(last)
-
1992
cc = lookup_unicode_combining_class(ch)
-
1992
if cc != 0 && last_cc != 0 && last_cc > cc
-
unpacked[i] = last
-
unpacked[i-1] = ch
-
i -= 1 if i > 1
-
else
-
1992
i += 1
-
end
-
end
-
148
return unpacked
-
end
-
2
(class <<self; private :unicode_sort_canonical; end)
-
-
1
def self.unicode_decompose(unpacked)
-
164
unpacked_result = []
-
164
for cp in unpacked
-
2140
if cp >= HANGUL_SBASE && cp < HANGUL_SBASE + HANGUL_SCOUNT
-
l, v, t = unicode_decompose_hangul(cp)
-
unpacked_result << l
-
unpacked_result << v if v
-
unpacked_result << t if t
-
else
-
2140
dc = lookup_unicode_compatibility(cp)
-
2140
unless dc
-
2140
unpacked_result << cp
-
else
-
unpacked_result.concat(unicode_decompose(dc.unpack("U*")))
-
end
-
end
-
end
-
164
return unpacked_result
-
end
-
2
(class <<self; private :unicode_decompose; end)
-
-
1
def self.unicode_decompose_hangul(codepoint)
-
sindex = codepoint - HANGUL_SBASE;
-
if sindex < 0 || sindex >= HANGUL_SCOUNT
-
l = codepoint
-
v = t = nil
-
return l, v, t
-
end
-
l = HANGUL_LBASE + sindex / HANGUL_NCOUNT
-
v = HANGUL_VBASE + (sindex % HANGUL_NCOUNT) / HANGUL_TCOUNT
-
t = HANGUL_TBASE + sindex % HANGUL_TCOUNT
-
if t == HANGUL_TBASE
-
t = nil
-
end
-
return l, v, t
-
end
-
2
(class <<self; private :unicode_decompose_hangul; end)
-
-
1
def self.lookup_unicode_combining_class(codepoint)
-
6124
codepoint_data = UNICODE_DATA[codepoint]
-
6124
(codepoint_data ?
-
3180
(codepoint_data[UNICODE_DATA_COMBINING_CLASS] || 0) :
-
2944
0)
-
end
-
2
(class <<self; private :lookup_unicode_combining_class; end)
-
-
1
def self.lookup_unicode_compatibility(codepoint)
-
2140
codepoint_data = UNICODE_DATA[codepoint]
-
2140
(codepoint_data ?
-
codepoint_data[UNICODE_DATA_COMPATIBILITY] : nil)
-
end
-
2
(class <<self; private :lookup_unicode_compatibility; end)
-
-
1
def self.lookup_unicode_lowercase(codepoint)
-
codepoint_data = UNICODE_DATA[codepoint]
-
(codepoint_data ?
-
(codepoint_data[UNICODE_DATA_LOWERCASE] || codepoint) :
-
codepoint)
-
end
-
2
(class <<self; private :lookup_unicode_lowercase; end)
-
-
1
def self.lookup_unicode_composition(unpacked)
-
1992
return COMPOSITION_TABLE[unpacked]
-
end
-
2
(class <<self; private :lookup_unicode_composition; end)
-
-
1
HANGUL_SBASE = 0xac00
-
1
HANGUL_LBASE = 0x1100
-
1
HANGUL_LCOUNT = 19
-
1
HANGUL_VBASE = 0x1161
-
1
HANGUL_VCOUNT = 21
-
1
HANGUL_TBASE = 0x11a7
-
1
HANGUL_TCOUNT = 28
-
1
HANGUL_NCOUNT = HANGUL_VCOUNT * HANGUL_TCOUNT # 588
-
1
HANGUL_SCOUNT = HANGUL_LCOUNT * HANGUL_NCOUNT # 11172
-
-
1
UNICODE_DATA_COMBINING_CLASS = 0
-
1
UNICODE_DATA_EXCLUSION = 1
-
1
UNICODE_DATA_CANONICAL = 2
-
1
UNICODE_DATA_COMPATIBILITY = 3
-
1
UNICODE_DATA_UPPERCASE = 4
-
1
UNICODE_DATA_LOWERCASE = 5
-
1
UNICODE_DATA_TITLECASE = 6
-
-
1
begin
-
1
if defined?(FakeFS)
-
fakefs_state = FakeFS.activated?
-
FakeFS.deactivate!
-
end
-
# This is a sparse Unicode table. Codepoints without entries are
-
# assumed to have the value: [0, 0, nil, nil, nil, nil, nil]
-
1
UNICODE_DATA = File.open(UNICODE_TABLE, "rb") do |file|
-
1
Marshal.load(file.read)
-
end
-
ensure
-
1
if defined?(FakeFS)
-
FakeFS.activate! if fakefs_state
-
end
-
end
-
-
1
COMPOSITION_TABLE = {}
-
1
for codepoint, data in UNICODE_DATA
-
4233
canonical = data[UNICODE_DATA_CANONICAL]
-
4233
exclusion = data[UNICODE_DATA_EXCLUSION]
-
-
4233
if canonical && exclusion == 0
-
918
COMPOSITION_TABLE[canonical.unpack("C*")] = codepoint
-
end
-
end
-
-
1
UNICODE_MAX_LENGTH = 256
-
1
ACE_MAX_LENGTH = 256
-
-
1
PUNYCODE_BASE = 36
-
1
PUNYCODE_TMIN = 1
-
1
PUNYCODE_TMAX = 26
-
1
PUNYCODE_SKEW = 38
-
1
PUNYCODE_DAMP = 700
-
1
PUNYCODE_INITIAL_BIAS = 72
-
1
PUNYCODE_INITIAL_N = 0x80
-
1
PUNYCODE_DELIMITER = 0x2D
-
-
1
PUNYCODE_MAXINT = 1 << 64
-
-
1
PUNYCODE_PRINT_ASCII =
-
"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" +
-
"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" +
-
" !\"\#$%&'()*+,-./" +
-
"0123456789:;<=>?" +
-
"@ABCDEFGHIJKLMNO" +
-
"PQRSTUVWXYZ[\\]^_" +
-
"`abcdefghijklmno" +
-
"pqrstuvwxyz{|}~\n"
-
-
# Input is invalid.
-
1
class PunycodeBadInput < StandardError; end
-
# Output would exceed the space provided.
-
1
class PunycodeBigOutput < StandardError; end
-
# Input needs wider integers to process.
-
1
class PunycodeOverflow < StandardError; end
-
-
1
def self.punycode_encode(unicode)
-
unicode = unicode.to_s unless unicode.is_a?(String)
-
input = unicode.unpack("U*")
-
output = [0] * (ACE_MAX_LENGTH + 1)
-
input_length = input.size
-
output_length = [ACE_MAX_LENGTH]
-
-
# Initialize the state
-
n = PUNYCODE_INITIAL_N
-
delta = out = 0
-
max_out = output_length[0]
-
bias = PUNYCODE_INITIAL_BIAS
-
-
# Handle the basic code points:
-
input_length.times do |j|
-
if punycode_basic?(input[j])
-
if max_out - out < 2
-
raise PunycodeBigOutput,
-
"Output would exceed the space provided."
-
end
-
output[out] = input[j]
-
out += 1
-
end
-
end
-
-
h = b = out
-
-
# h is the number of code points that have been handled, b is the
-
# number of basic code points, and out is the number of characters
-
# that have been output.
-
-
if b > 0
-
output[out] = PUNYCODE_DELIMITER
-
out += 1
-
end
-
-
# Main encoding loop:
-
-
while h < input_length
-
# All non-basic code points < n have been
-
# handled already. Find the next larger one:
-
-
m = PUNYCODE_MAXINT
-
input_length.times do |j|
-
m = input[j] if (n...m) === input[j]
-
end
-
-
# Increase delta enough to advance the decoder's
-
# <n,i> state to <m,0>, but guard against overflow:
-
-
if m - n > (PUNYCODE_MAXINT - delta) / (h + 1)
-
raise PunycodeOverflow, "Input needs wider integers to process."
-
end
-
delta += (m - n) * (h + 1)
-
n = m
-
-
input_length.times do |j|
-
# Punycode does not need to check whether input[j] is basic:
-
if input[j] < n
-
delta += 1
-
if delta == 0
-
raise PunycodeOverflow,
-
"Input needs wider integers to process."
-
end
-
end
-
-
if input[j] == n
-
# Represent delta as a generalized variable-length integer:
-
-
q = delta; k = PUNYCODE_BASE
-
while true
-
if out >= max_out
-
raise PunycodeBigOutput,
-
"Output would exceed the space provided."
-
end
-
t = (
-
if k <= bias
-
PUNYCODE_TMIN
-
elsif k >= bias + PUNYCODE_TMAX
-
PUNYCODE_TMAX
-
else
-
k - bias
-
end
-
)
-
break if q < t
-
output[out] =
-
punycode_encode_digit(t + (q - t) % (PUNYCODE_BASE - t))
-
out += 1
-
q = (q - t) / (PUNYCODE_BASE - t)
-
k += PUNYCODE_BASE
-
end
-
-
output[out] = punycode_encode_digit(q)
-
out += 1
-
bias = punycode_adapt(delta, h + 1, h == b)
-
delta = 0
-
h += 1
-
end
-
end
-
-
delta += 1
-
n += 1
-
end
-
-
output_length[0] = out
-
-
outlen = out
-
outlen.times do |j|
-
c = output[j]
-
unless c >= 0 && c <= 127
-
raise StandardError, "Invalid output char."
-
end
-
unless PUNYCODE_PRINT_ASCII[c]
-
raise PunycodeBadInput, "Input is invalid."
-
end
-
end
-
-
output[0..outlen].map { |x| x.chr }.join("").sub(/\0+\z/, "")
-
end
-
2
(class <<self; private :punycode_encode; end)
-
-
1
def self.punycode_decode(punycode)
-
input = []
-
output = []
-
-
if ACE_MAX_LENGTH * 2 < punycode.size
-
raise PunycodeBigOutput, "Output would exceed the space provided."
-
end
-
punycode.each_byte do |c|
-
unless c >= 0 && c <= 127
-
raise PunycodeBadInput, "Input is invalid."
-
end
-
input.push(c)
-
end
-
-
input_length = input.length
-
output_length = [UNICODE_MAX_LENGTH]
-
-
# Initialize the state
-
n = PUNYCODE_INITIAL_N
-
-
out = i = 0
-
max_out = output_length[0]
-
bias = PUNYCODE_INITIAL_BIAS
-
-
# Handle the basic code points: Let b be the number of input code
-
# points before the last delimiter, or 0 if there is none, then
-
# copy the first b code points to the output.
-
-
b = 0
-
input_length.times do |j|
-
b = j if punycode_delimiter?(input[j])
-
end
-
if b > max_out
-
raise PunycodeBigOutput, "Output would exceed the space provided."
-
end
-
-
b.times do |j|
-
unless punycode_basic?(input[j])
-
raise PunycodeBadInput, "Input is invalid."
-
end
-
output[out] = input[j]
-
out+=1
-
end
-
-
# Main decoding loop: Start just after the last delimiter if any
-
# basic code points were copied; start at the beginning otherwise.
-
-
in_ = b > 0 ? b + 1 : 0
-
while in_ < input_length
-
-
# in_ is the index of the next character to be consumed, and
-
# out is the number of code points in the output array.
-
-
# Decode a generalized variable-length integer into delta,
-
# which gets added to i. The overflow checking is easier
-
# if we increase i as we go, then subtract off its starting
-
# value at the end to obtain delta.
-
-
oldi = i; w = 1; k = PUNYCODE_BASE
-
while true
-
if in_ >= input_length
-
raise PunycodeBadInput, "Input is invalid."
-
end
-
digit = punycode_decode_digit(input[in_])
-
in_+=1
-
if digit >= PUNYCODE_BASE
-
raise PunycodeBadInput, "Input is invalid."
-
end
-
if digit > (PUNYCODE_MAXINT - i) / w
-
raise PunycodeOverflow, "Input needs wider integers to process."
-
end
-
i += digit * w
-
t = (
-
if k <= bias
-
PUNYCODE_TMIN
-
elsif k >= bias + PUNYCODE_TMAX
-
PUNYCODE_TMAX
-
else
-
k - bias
-
end
-
)
-
break if digit < t
-
if w > PUNYCODE_MAXINT / (PUNYCODE_BASE - t)
-
raise PunycodeOverflow, "Input needs wider integers to process."
-
end
-
w *= PUNYCODE_BASE - t
-
k += PUNYCODE_BASE
-
end
-
-
bias = punycode_adapt(i - oldi, out + 1, oldi == 0)
-
-
# I was supposed to wrap around from out + 1 to 0,
-
# incrementing n each time, so we'll fix that now:
-
-
if i / (out + 1) > PUNYCODE_MAXINT - n
-
raise PunycodeOverflow, "Input needs wider integers to process."
-
end
-
n += i / (out + 1)
-
i %= out + 1
-
-
# Insert n at position i of the output:
-
-
# not needed for Punycode:
-
# raise PUNYCODE_INVALID_INPUT if decode_digit(n) <= base
-
if out >= max_out
-
raise PunycodeBigOutput, "Output would exceed the space provided."
-
end
-
-
#memmove(output + i + 1, output + i, (out - i) * sizeof *output)
-
output[i + 1, out - i] = output[i, out - i]
-
output[i] = n
-
i += 1
-
-
out += 1
-
end
-
-
output_length[0] = out
-
-
output.pack("U*")
-
end
-
2
(class <<self; private :punycode_decode; end)
-
-
1
def self.punycode_basic?(codepoint)
-
codepoint < 0x80
-
end
-
2
(class <<self; private :punycode_basic?; end)
-
-
1
def self.punycode_delimiter?(codepoint)
-
codepoint == PUNYCODE_DELIMITER
-
end
-
2
(class <<self; private :punycode_delimiter?; end)
-
-
1
def self.punycode_encode_digit(d)
-
d + 22 + 75 * ((d < 26) ? 1 : 0)
-
end
-
2
(class <<self; private :punycode_encode_digit; end)
-
-
# Returns the numeric value of a basic codepoint
-
# (for use in representing integers) in the range 0 to
-
# base - 1, or PUNYCODE_BASE if codepoint does not represent a value.
-
1
def self.punycode_decode_digit(codepoint)
-
if codepoint - 48 < 10
-
codepoint - 22
-
elsif codepoint - 65 < 26
-
codepoint - 65
-
elsif codepoint - 97 < 26
-
codepoint - 97
-
else
-
PUNYCODE_BASE
-
end
-
end
-
2
(class <<self; private :punycode_decode_digit; end)
-
-
# Bias adaptation method
-
1
def self.punycode_adapt(delta, numpoints, firsttime)
-
delta = firsttime ? delta / PUNYCODE_DAMP : delta >> 1
-
# delta >> 1 is a faster way of doing delta / 2
-
delta += delta / numpoints
-
difference = PUNYCODE_BASE - PUNYCODE_TMIN
-
-
k = 0
-
while delta > (difference * PUNYCODE_TMAX) / 2
-
delta /= difference
-
k += PUNYCODE_BASE
-
end
-
-
k + (difference + 1) * delta / (delta + PUNYCODE_SKEW)
-
end
-
2
(class <<self; private :punycode_adapt; end)
-
end
-
# :startdoc:
-
end
-
# encoding:utf-8
-
#--
-
# Copyright (C) 2006-2015 Bob Aman
-
#
-
# Licensed under the Apache License, Version 2.0 (the "License");
-
# you may not use this file except in compliance with the License.
-
# You may obtain a copy of the License at
-
#
-
# http://www.apache.org/licenses/LICENSE-2.0
-
#
-
# Unless required by applicable law or agreed to in writing, software
-
# distributed under the License is distributed on an "AS IS" BASIS,
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-
# See the License for the specific language governing permissions and
-
# limitations under the License.
-
#++
-
-
-
1
require "addressable/version"
-
1
require "addressable/uri"
-
-
1
module Addressable
-
##
-
# This is an implementation of a URI template based on
-
# RFC 6570 (http://tools.ietf.org/html/rfc6570).
-
1
class Template
-
# Constants used throughout the template code.
-
1
anything =
-
Addressable::URI::CharacterClasses::RESERVED +
-
Addressable::URI::CharacterClasses::UNRESERVED
-
-
-
1
variable_char_class =
-
Addressable::URI::CharacterClasses::ALPHA +
-
Addressable::URI::CharacterClasses::DIGIT + '_'
-
-
1
var_char =
-
"(?:(?:[#{variable_char_class}]|%[a-fA-F0-9][a-fA-F0-9])+)"
-
1
RESERVED =
-
"(?:[#{anything}]|%[a-fA-F0-9][a-fA-F0-9])"
-
1
UNRESERVED =
-
"(?:[#{
-
Addressable::URI::CharacterClasses::UNRESERVED
-
}]|%[a-fA-F0-9][a-fA-F0-9])"
-
1
variable =
-
"(?:#{var_char}(?:\\.?#{var_char})*)"
-
1
varspec =
-
"(?:(#{variable})(\\*|:\\d+)?)"
-
1
VARNAME =
-
/^#{variable}$/
-
1
VARSPEC =
-
/^#{varspec}$/
-
1
VARIABLE_LIST =
-
/^#{varspec}(?:,#{varspec})*$/
-
1
operator =
-
"+#./;?&=,!@|"
-
1
EXPRESSION =
-
/\{([#{operator}])?(#{varspec}(?:,#{varspec})*)\}/
-
-
-
1
LEADERS = {
-
'?' => '?',
-
'/' => '/',
-
'#' => '#',
-
'.' => '.',
-
';' => ';',
-
'&' => '&'
-
}
-
1
JOINERS = {
-
'?' => '&',
-
'.' => '.',
-
';' => ';',
-
'&' => '&',
-
'/' => '/'
-
}
-
-
##
-
# Raised if an invalid template value is supplied.
-
1
class InvalidTemplateValueError < StandardError
-
end
-
-
##
-
# Raised if an invalid template operator is used in a pattern.
-
1
class InvalidTemplateOperatorError < StandardError
-
end
-
-
##
-
# Raised if an invalid template operator is used in a pattern.
-
1
class TemplateOperatorAbortedError < StandardError
-
end
-
-
##
-
# This class represents the data that is extracted when a Template
-
# is matched against a URI.
-
1
class MatchData
-
##
-
# Creates a new MatchData object.
-
# MatchData objects should never be instantiated directly.
-
#
-
# @param [Addressable::URI] uri
-
# The URI that the template was matched against.
-
1
def initialize(uri, template, mapping)
-
@uri = uri.dup.freeze
-
@template = template
-
@mapping = mapping.dup.freeze
-
end
-
-
##
-
# @return [Addressable::URI]
-
# The URI that the Template was matched against.
-
1
attr_reader :uri
-
-
##
-
# @return [Addressable::Template]
-
# The Template used for the match.
-
1
attr_reader :template
-
-
##
-
# @return [Hash]
-
# The mapping that resulted from the match.
-
# Note that this mapping does not include keys or values for
-
# variables that appear in the Template, but are not present
-
# in the URI.
-
1
attr_reader :mapping
-
-
##
-
# @return [Array]
-
# The list of variables that were present in the Template.
-
# Note that this list will include variables which do not appear
-
# in the mapping because they were not present in URI.
-
1
def variables
-
self.template.variables
-
end
-
1
alias_method :keys, :variables
-
1
alias_method :names, :variables
-
-
##
-
# @return [Array]
-
# The list of values that were captured by the Template.
-
# Note that this list will include nils for any variables which
-
# were in the Template, but did not appear in the URI.
-
1
def values
-
@values ||= self.variables.inject([]) do |accu, key|
-
accu << self.mapping[key]
-
accu
-
end
-
end
-
1
alias_method :captures, :values
-
-
##
-
# Accesses captured values by name or by index.
-
#
-
# @param [String, Symbol, Fixnum] key
-
# Capture index or name. Note that when accessing by with index
-
# of 0, the full URI will be returned. The intention is to mimic
-
# the ::MatchData#[] behavior.
-
#
-
# @param [#to_int, nil] len
-
# If provided, an array of values will be returend with the given
-
# parameter used as length.
-
#
-
# @return [Array, String, nil]
-
# The captured value corresponding to the index or name. If the
-
# value was not provided or the key is unknown, nil will be
-
# returned.
-
#
-
# If the second parameter is provided, an array of that length will
-
# be returned instead.
-
1
def [](key, len = nil)
-
if len
-
to_a[key, len]
-
elsif String === key or Symbol === key
-
mapping[key.to_s]
-
else
-
to_a[key]
-
end
-
end
-
-
##
-
# @return [Array]
-
# Array with the matched URI as first element followed by the captured
-
# values.
-
1
def to_a
-
[to_s, *values]
-
end
-
-
##
-
# @return [String]
-
# The matched URI as String.
-
1
def to_s
-
uri.to_s
-
end
-
1
alias_method :string, :to_s
-
-
# Returns multiple captured values at once.
-
#
-
# @param [String, Symbol, Fixnum] *indexes
-
# Indices of the captures to be returned
-
#
-
# @return [Array]
-
# Values corresponding to given indices.
-
#
-
# @see Addressable::Template::MatchData#[]
-
1
def values_at(*indexes)
-
indexes.map { |i| self[i] }
-
end
-
-
##
-
# Returns a <tt>String</tt> representation of the MatchData's state.
-
#
-
# @return [String] The MatchData's state, as a <tt>String</tt>.
-
1
def inspect
-
sprintf("#<%s:%#0x RESULT:%s>",
-
self.class.to_s, self.object_id, self.mapping.inspect)
-
end
-
-
##
-
# Dummy method for code expecting a ::MatchData instance
-
#
-
# @return [String] An empty string.
-
1
def pre_match
-
""
-
end
-
1
alias_method :post_match, :pre_match
-
end
-
-
##
-
# Creates a new <tt>Addressable::Template</tt> object.
-
#
-
# @param [#to_str] pattern The URI Template pattern.
-
#
-
# @return [Addressable::Template] The initialized Template object.
-
1
def initialize(pattern)
-
if !pattern.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{pattern.class} into String."
-
end
-
@pattern = pattern.to_str.dup.freeze
-
end
-
-
##
-
# Freeze URI, initializing instance variables.
-
#
-
# @return [Addressable::URI] The frozen URI object.
-
1
def freeze
-
self.variables
-
self.variable_defaults
-
self.named_captures
-
super
-
end
-
-
##
-
# @return [String] The Template object's pattern.
-
1
attr_reader :pattern
-
-
##
-
# Returns a <tt>String</tt> representation of the Template object's state.
-
#
-
# @return [String] The Template object's state, as a <tt>String</tt>.
-
1
def inspect
-
sprintf("#<%s:%#0x PATTERN:%s>",
-
self.class.to_s, self.object_id, self.pattern)
-
end
-
-
##
-
# Returns <code>true</code> if the Template objects are equal. This method
-
# does NOT normalize either Template before doing the comparison.
-
#
-
# @param [Object] template The Template to compare.
-
#
-
# @return [TrueClass, FalseClass]
-
# <code>true</code> if the Templates are equivalent, <code>false</code>
-
# otherwise.
-
1
def ==(template)
-
return false unless template.kind_of?(Template)
-
return self.pattern == template.pattern
-
end
-
-
##
-
# Addressable::Template makes no distinction between `==` and `eql?`.
-
#
-
# @see #==
-
1
alias_method :eql?, :==
-
-
##
-
# Extracts a mapping from the URI using a URI Template pattern.
-
#
-
# @param [Addressable::URI, #to_str] uri
-
# The URI to extract from.
-
#
-
# @param [#restore, #match] processor
-
# A template processor object may optionally be supplied.
-
#
-
# The object should respond to either the <tt>restore</tt> or
-
# <tt>match</tt> messages or both. The <tt>restore</tt> method should
-
# take two parameters: `[String] name` and `[String] value`.
-
# The <tt>restore</tt> method should reverse any transformations that
-
# have been performed on the value to ensure a valid URI.
-
# The <tt>match</tt> method should take a single
-
# parameter: `[String] name`. The <tt>match</tt> method should return
-
# a <tt>String</tt> containing a regular expression capture group for
-
# matching on that particular variable. The default value is `".*?"`.
-
# The <tt>match</tt> method has no effect on multivariate operator
-
# expansions.
-
#
-
# @return [Hash, NilClass]
-
# The <tt>Hash</tt> mapping that was extracted from the URI, or
-
# <tt>nil</tt> if the URI didn't match the template.
-
#
-
# @example
-
# class ExampleProcessor
-
# def self.restore(name, value)
-
# return value.gsub(/\+/, " ") if name == "query"
-
# return value
-
# end
-
#
-
# def self.match(name)
-
# return ".*?" if name == "first"
-
# return ".*"
-
# end
-
# end
-
#
-
# uri = Addressable::URI.parse(
-
# "http://example.com/search/an+example+search+query/"
-
# )
-
# Addressable::Template.new(
-
# "http://example.com/search/{query}/"
-
# ).extract(uri, ExampleProcessor)
-
# #=> {"query" => "an example search query"}
-
#
-
# uri = Addressable::URI.parse("http://example.com/a/b/c/")
-
# Addressable::Template.new(
-
# "http://example.com/{first}/{second}/"
-
# ).extract(uri, ExampleProcessor)
-
# #=> {"first" => "a", "second" => "b/c"}
-
#
-
# uri = Addressable::URI.parse("http://example.com/a/b/c/")
-
# Addressable::Template.new(
-
# "http://example.com/{first}/{-list|/|second}/"
-
# ).extract(uri)
-
# #=> {"first" => "a", "second" => ["b", "c"]}
-
1
def extract(uri, processor=nil)
-
match_data = self.match(uri, processor)
-
return (match_data ? match_data.mapping : nil)
-
end
-
-
##
-
# Extracts match data from the URI using a URI Template pattern.
-
#
-
# @param [Addressable::URI, #to_str] uri
-
# The URI to extract from.
-
#
-
# @param [#restore, #match] processor
-
# A template processor object may optionally be supplied.
-
#
-
# The object should respond to either the <tt>restore</tt> or
-
# <tt>match</tt> messages or both. The <tt>restore</tt> method should
-
# take two parameters: `[String] name` and `[String] value`.
-
# The <tt>restore</tt> method should reverse any transformations that
-
# have been performed on the value to ensure a valid URI.
-
# The <tt>match</tt> method should take a single
-
# parameter: `[String] name`. The <tt>match</tt> method should return
-
# a <tt>String</tt> containing a regular expression capture group for
-
# matching on that particular variable. The default value is `".*?"`.
-
# The <tt>match</tt> method has no effect on multivariate operator
-
# expansions.
-
#
-
# @return [Hash, NilClass]
-
# The <tt>Hash</tt> mapping that was extracted from the URI, or
-
# <tt>nil</tt> if the URI didn't match the template.
-
#
-
# @example
-
# class ExampleProcessor
-
# def self.restore(name, value)
-
# return value.gsub(/\+/, " ") if name == "query"
-
# return value
-
# end
-
#
-
# def self.match(name)
-
# return ".*?" if name == "first"
-
# return ".*"
-
# end
-
# end
-
#
-
# uri = Addressable::URI.parse(
-
# "http://example.com/search/an+example+search+query/"
-
# )
-
# match = Addressable::Template.new(
-
# "http://example.com/search/{query}/"
-
# ).match(uri, ExampleProcessor)
-
# match.variables
-
# #=> ["query"]
-
# match.captures
-
# #=> ["an example search query"]
-
#
-
# uri = Addressable::URI.parse("http://example.com/a/b/c/")
-
# match = Addressable::Template.new(
-
# "http://example.com/{first}/{+second}/"
-
# ).match(uri, ExampleProcessor)
-
# match.variables
-
# #=> ["first", "second"]
-
# match.captures
-
# #=> ["a", "b/c"]
-
#
-
# uri = Addressable::URI.parse("http://example.com/a/b/c/")
-
# match = Addressable::Template.new(
-
# "http://example.com/{first}{/second*}/"
-
# ).match(uri)
-
# match.variables
-
# #=> ["first", "second"]
-
# match.captures
-
# #=> ["a", ["b", "c"]]
-
1
def match(uri, processor=nil)
-
uri = Addressable::URI.parse(uri)
-
mapping = {}
-
-
# First, we need to process the pattern, and extract the values.
-
expansions, expansion_regexp =
-
parse_template_pattern(pattern, processor)
-
-
return nil unless uri.to_str.match(expansion_regexp)
-
unparsed_values = uri.to_str.scan(expansion_regexp).flatten
-
-
if uri.to_str == pattern
-
return Addressable::Template::MatchData.new(uri, self, mapping)
-
elsif expansions.size > 0
-
index = 0
-
expansions.each do |expansion|
-
_, operator, varlist = *expansion.match(EXPRESSION)
-
varlist.split(',').each do |varspec|
-
_, name, modifier = *varspec.match(VARSPEC)
-
mapping[name] ||= nil
-
case operator
-
when nil, '+', '#', '/', '.'
-
unparsed_value = unparsed_values[index]
-
name = varspec[VARSPEC, 1]
-
value = unparsed_value
-
value = value.split(JOINERS[operator]) if value && modifier == '*'
-
when ';', '?', '&'
-
if modifier == '*'
-
if unparsed_values[index]
-
value = unparsed_values[index].split(JOINERS[operator])
-
value = value.inject({}) do |acc, v|
-
key, val = v.split('=')
-
val = "" if val.nil?
-
acc[key] = val
-
acc
-
end
-
end
-
else
-
if (unparsed_values[index])
-
name, value = unparsed_values[index].split('=')
-
value = "" if value.nil?
-
end
-
end
-
end
-
if processor != nil && processor.respond_to?(:restore)
-
value = processor.restore(name, value)
-
end
-
if processor == nil
-
if value.is_a?(Hash)
-
value = value.inject({}){|acc, (k, v)|
-
acc[Addressable::URI.unencode_component(k)] =
-
Addressable::URI.unencode_component(v)
-
acc
-
}
-
elsif value.is_a?(Array)
-
value = value.map{|v| Addressable::URI.unencode_component(v) }
-
else
-
value = Addressable::URI.unencode_component(value)
-
end
-
end
-
if !mapping.has_key?(name) || mapping[name].nil?
-
# Doesn't exist, set to value (even if value is nil)
-
mapping[name] = value
-
end
-
index = index + 1
-
end
-
end
-
return Addressable::Template::MatchData.new(uri, self, mapping)
-
else
-
return nil
-
end
-
end
-
-
##
-
# Expands a URI template into another URI template.
-
#
-
# @param [Hash] mapping The mapping that corresponds to the pattern.
-
# @param [#validate, #transform] processor
-
# An optional processor object may be supplied.
-
#
-
# The object should respond to either the <tt>validate</tt> or
-
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
-
# <tt>transform</tt> methods should take two parameters: <tt>name</tt> and
-
# <tt>value</tt>. The <tt>validate</tt> method should return <tt>true</tt>
-
# or <tt>false</tt>; <tt>true</tt> if the value of the variable is valid,
-
# <tt>false</tt> otherwise. An <tt>InvalidTemplateValueError</tt>
-
# exception will be raised if the value is invalid. The <tt>transform</tt>
-
# method should return the transformed variable value as a <tt>String</tt>.
-
# If a <tt>transform</tt> method is used, the value will not be percent
-
# encoded automatically. Unicode normalization will be performed both
-
# before and after sending the value to the transform method.
-
#
-
# @return [Addressable::Template] The partially expanded URI template.
-
#
-
# @example
-
# Addressable::Template.new(
-
# "http://example.com/{one}/{two}/"
-
# ).partial_expand({"one" => "1"}).pattern
-
# #=> "http://example.com/1/{two}/"
-
#
-
# Addressable::Template.new(
-
# "http://example.com/{?one,two}/"
-
# ).partial_expand({"one" => "1"}).pattern
-
# #=> "http://example.com/?one=1{&two}/"
-
#
-
# Addressable::Template.new(
-
# "http://example.com/{?one,two,three}/"
-
# ).partial_expand({"one" => "1", "three" => 3}).pattern
-
# #=> "http://example.com/?one=1{&two}&three=3"
-
1
def partial_expand(mapping, processor=nil)
-
result = self.pattern.dup
-
mapping = normalize_keys(mapping)
-
result.gsub!( EXPRESSION ) do |capture|
-
transform_partial_capture(mapping, capture, processor)
-
end
-
return Addressable::Template.new(result)
-
end
-
-
##
-
# Expands a URI template into a full URI.
-
#
-
# @param [Hash] mapping The mapping that corresponds to the pattern.
-
# @param [#validate, #transform] processor
-
# An optional processor object may be supplied.
-
#
-
# The object should respond to either the <tt>validate</tt> or
-
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
-
# <tt>transform</tt> methods should take two parameters: <tt>name</tt> and
-
# <tt>value</tt>. The <tt>validate</tt> method should return <tt>true</tt>
-
# or <tt>false</tt>; <tt>true</tt> if the value of the variable is valid,
-
# <tt>false</tt> otherwise. An <tt>InvalidTemplateValueError</tt>
-
# exception will be raised if the value is invalid. The <tt>transform</tt>
-
# method should return the transformed variable value as a <tt>String</tt>.
-
# If a <tt>transform</tt> method is used, the value will not be percent
-
# encoded automatically. Unicode normalization will be performed both
-
# before and after sending the value to the transform method.
-
#
-
# @return [Addressable::URI] The expanded URI template.
-
#
-
# @example
-
# class ExampleProcessor
-
# def self.validate(name, value)
-
# return !!(value =~ /^[\w ]+$/) if name == "query"
-
# return true
-
# end
-
#
-
# def self.transform(name, value)
-
# return value.gsub(/ /, "+") if name == "query"
-
# return value
-
# end
-
# end
-
#
-
# Addressable::Template.new(
-
# "http://example.com/search/{query}/"
-
# ).expand(
-
# {"query" => "an example search query"},
-
# ExampleProcessor
-
# ).to_str
-
# #=> "http://example.com/search/an+example+search+query/"
-
#
-
# Addressable::Template.new(
-
# "http://example.com/search/{query}/"
-
# ).expand(
-
# {"query" => "an example search query"}
-
# ).to_str
-
# #=> "http://example.com/search/an%20example%20search%20query/"
-
#
-
# Addressable::Template.new(
-
# "http://example.com/search/{query}/"
-
# ).expand(
-
# {"query" => "bogus!"},
-
# ExampleProcessor
-
# ).to_str
-
# #=> Addressable::Template::InvalidTemplateValueError
-
1
def expand(mapping, processor=nil)
-
result = self.pattern.dup
-
mapping = normalize_keys(mapping)
-
result.gsub!( EXPRESSION ) do |capture|
-
transform_capture(mapping, capture, processor)
-
end
-
return Addressable::URI.parse(result)
-
end
-
-
##
-
# Returns an Array of variables used within the template pattern.
-
# The variables are listed in the Array in the order they appear within
-
# the pattern. Multiple occurrences of a variable within a pattern are
-
# not represented in this Array.
-
#
-
# @return [Array] The variables present in the template's pattern.
-
1
def variables
-
@variables ||= ordered_variable_defaults.map { |var, val| var }.uniq
-
end
-
1
alias_method :keys, :variables
-
1
alias_method :names, :variables
-
-
##
-
# Returns a mapping of variables to their default values specified
-
# in the template. Variables without defaults are not returned.
-
#
-
# @return [Hash] Mapping of template variables to their defaults
-
1
def variable_defaults
-
@variable_defaults ||=
-
Hash[*ordered_variable_defaults.reject { |k, v| v.nil? }.flatten]
-
end
-
-
##
-
# Coerces a template into a `Regexp` object. This regular expression will
-
# behave very similarly to the actual template, and should match the same
-
# URI values, but it cannot fully handle, for example, values that would
-
# extract to an `Array`.
-
#
-
# @return [Regexp] A regular expression which should match the template.
-
1
def to_regexp
-
_, source = parse_template_pattern(pattern)
-
Regexp.new(source)
-
end
-
-
##
-
# Returns the source of the coerced `Regexp`.
-
#
-
# @return [String] The source of the `Regexp` given by {#to_regexp}.
-
#
-
# @api private
-
1
def source
-
self.to_regexp.source
-
end
-
-
##
-
# Returns the named captures of the coerced `Regexp`.
-
#
-
# @return [Hash] The named captures of the `Regexp` given by {#to_regexp}.
-
#
-
# @api private
-
1
def named_captures
-
self.to_regexp.named_captures
-
end
-
-
##
-
# Generates a route result for a given set of parameters.
-
# Should only be used by rack-mount.
-
#
-
# @param params [Hash] The set of parameters used to expand the template.
-
# @param recall [Hash] Default parameters used to expand the template.
-
# @param options [Hash] Either a `:processor` or a `:parameterize` block.
-
#
-
# @api private
-
1
def generate(params={}, recall={}, options={})
-
merged = recall.merge(params)
-
if options[:processor]
-
processor = options[:processor]
-
elsif options[:parameterize]
-
# TODO: This is sending me into fits trying to shoe-horn this into
-
# the existing API. I think I've got this backwards and processors
-
# should be a set of 4 optional blocks named :validate, :transform,
-
# :match, and :restore. Having to use a singleton here is a huge
-
# code smell.
-
processor = Object.new
-
class <<processor
-
attr_accessor :block
-
def transform(name, value)
-
block.call(name, value)
-
end
-
end
-
processor.block = options[:parameterize]
-
else
-
processor = nil
-
end
-
result = self.expand(merged, processor)
-
result.to_s if result
-
end
-
-
1
private
-
1
def ordered_variable_defaults
-
@ordered_variable_defaults ||= begin
-
expansions, _ = parse_template_pattern(pattern)
-
expansions.map do |capture|
-
_, _, varlist = *capture.match(EXPRESSION)
-
varlist.split(',').map do |varspec|
-
varspec[VARSPEC, 1]
-
end
-
end.flatten
-
end
-
end
-
-
-
##
-
# Loops through each capture and expands any values available in mapping
-
#
-
# @param [Hash] mapping
-
# Set of keys to expand
-
# @param [String] capture
-
# The expression to expand
-
# @param [#validate, #transform] processor
-
# An optional processor object may be supplied.
-
#
-
# The object should respond to either the <tt>validate</tt> or
-
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
-
# <tt>transform</tt> methods should take two parameters: <tt>name</tt> and
-
# <tt>value</tt>. The <tt>validate</tt> method should return <tt>true</tt>
-
# or <tt>false</tt>; <tt>true</tt> if the value of the variable is valid,
-
# <tt>false</tt> otherwise. An <tt>InvalidTemplateValueError</tt> exception
-
# will be raised if the value is invalid. The <tt>transform</tt> method
-
# should return the transformed variable value as a <tt>String</tt>. If a
-
# <tt>transform</tt> method is used, the value will not be percent encoded
-
# automatically. Unicode normalization will be performed both before and
-
# after sending the value to the transform method.
-
#
-
# @return [String] The expanded expression
-
1
def transform_partial_capture(mapping, capture, processor = nil)
-
_, operator, varlist = *capture.match(EXPRESSION)
-
-
vars = varlist.split(',')
-
-
if '?' == operator
-
# partial expansion of form style query variables sometimes requires a
-
# slight reordering of the variables to produce a valid url.
-
first_to_expand = vars.find { |varspec|
-
_, name, _ = *varspec.match(VARSPEC)
-
mapping.key? name
-
}
-
-
vars = [first_to_expand] + vars.reject {|varspec| varspec == first_to_expand} if first_to_expand
-
end
-
-
vars
-
.zip(operator_sequence(operator).take(vars.length))
-
.reduce("") do |acc, (varspec, op)|
-
_, name, _ = *varspec.match(VARSPEC)
-
-
acc << if mapping.key? name
-
transform_capture(mapping, "{#{op}#{varspec}}", processor)
-
else
-
"{#{op}#{varspec}}"
-
end
-
end
-
end
-
-
##
-
# Creates a lazy Enumerator of the operators that should be used to expand
-
# variables in a varlist starting with `operator`. For example, an operator
-
# `"?"` results in the sequence `"?","&","&"...`
-
#
-
# @param [String] operator from which to generate a sequence
-
#
-
# @return [Enumerator] sequence of operators
-
1
def operator_sequence(operator)
-
rest_operator = if "?" == operator
-
"&"
-
else
-
operator
-
end
-
head_operator = operator
-
-
Enumerator.new do |y|
-
y << head_operator.to_s
-
while true
-
y << rest_operator.to_s
-
end
-
end
-
end
-
-
##
-
# Transforms a mapped value so that values can be substituted into the
-
# template.
-
#
-
# @param [Hash] mapping The mapping to replace captures
-
# @param [String] capture
-
# The expression to replace
-
# @param [#validate, #transform] processor
-
# An optional processor object may be supplied.
-
#
-
# The object should respond to either the <tt>validate</tt> or
-
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
-
# <tt>transform</tt> methods should take two parameters: <tt>name</tt> and
-
# <tt>value</tt>. The <tt>validate</tt> method should return <tt>true</tt>
-
# or <tt>false</tt>; <tt>true</tt> if the value of the variable is valid,
-
# <tt>false</tt> otherwise. An <tt>InvalidTemplateValueError</tt> exception
-
# will be raised if the value is invalid. The <tt>transform</tt> method
-
# should return the transformed variable value as a <tt>String</tt>. If a
-
# <tt>transform</tt> method is used, the value will not be percent encoded
-
# automatically. Unicode normalization will be performed both before and
-
# after sending the value to the transform method.
-
#
-
# @return [String] The expanded expression
-
1
def transform_capture(mapping, capture, processor=nil)
-
_, operator, varlist = *capture.match(EXPRESSION)
-
return_value = varlist.split(',').inject([]) do |acc, varspec|
-
_, name, modifier = *varspec.match(VARSPEC)
-
value = mapping[name]
-
unless value == nil || value == {}
-
allow_reserved = %w(+ #).include?(operator)
-
# Common primitives where the .to_s output is well-defined
-
if Numeric === value || Symbol === value ||
-
value == true || value == false
-
value = value.to_s
-
end
-
length = modifier.gsub(':', '').to_i if modifier =~ /^:\d+/
-
-
unless (Hash === value) ||
-
value.respond_to?(:to_ary) || value.respond_to?(:to_str)
-
raise TypeError,
-
"Can't convert #{value.class} into String or Array."
-
end
-
-
value = normalize_value(value)
-
-
if processor == nil || !processor.respond_to?(:transform)
-
# Handle percent escaping
-
if allow_reserved
-
encode_map =
-
Addressable::URI::CharacterClasses::RESERVED +
-
Addressable::URI::CharacterClasses::UNRESERVED
-
else
-
encode_map = Addressable::URI::CharacterClasses::UNRESERVED
-
end
-
if value.kind_of?(Array)
-
transformed_value = value.map do |val|
-
if length
-
Addressable::URI.encode_component(val[0...length], encode_map)
-
else
-
Addressable::URI.encode_component(val, encode_map)
-
end
-
end
-
unless modifier == "*"
-
transformed_value = transformed_value.join(',')
-
end
-
elsif value.kind_of?(Hash)
-
transformed_value = value.map do |key, val|
-
if modifier == "*"
-
"#{
-
Addressable::URI.encode_component( key, encode_map)
-
}=#{
-
Addressable::URI.encode_component( val, encode_map)
-
}"
-
else
-
"#{
-
Addressable::URI.encode_component( key, encode_map)
-
},#{
-
Addressable::URI.encode_component( val, encode_map)
-
}"
-
end
-
end
-
unless modifier == "*"
-
transformed_value = transformed_value.join(',')
-
end
-
else
-
if length
-
transformed_value = Addressable::URI.encode_component(
-
value[0...length], encode_map)
-
else
-
transformed_value = Addressable::URI.encode_component(
-
value, encode_map)
-
end
-
end
-
end
-
-
# Process, if we've got a processor
-
if processor != nil
-
if processor.respond_to?(:validate)
-
if !processor.validate(name, value)
-
display_value = value.kind_of?(Array) ? value.inspect : value
-
raise InvalidTemplateValueError,
-
"#{name}=#{display_value} is an invalid template value."
-
end
-
end
-
if processor.respond_to?(:transform)
-
transformed_value = processor.transform(name, value)
-
transformed_value = normalize_value(transformed_value)
-
end
-
end
-
acc << [name, transformed_value]
-
end
-
acc
-
end
-
return "" if return_value.empty?
-
join_values(operator, return_value)
-
end
-
-
##
-
# Takes a set of values, and joins them together based on the
-
# operator.
-
#
-
# @param [String, Nil] operator One of the operators from the set
-
# (?,&,+,#,;,/,.), or nil if there wasn't one.
-
# @param [Array] return_value
-
# The set of return values (as [variable_name, value] tuples) that will
-
# be joined together.
-
#
-
# @return [String] The transformed mapped value
-
1
def join_values(operator, return_value)
-
leader = LEADERS.fetch(operator, '')
-
joiner = JOINERS.fetch(operator, ',')
-
case operator
-
when '&', '?'
-
leader + return_value.map{|k,v|
-
if v.is_a?(Array) && v.first =~ /=/
-
v.join(joiner)
-
elsif v.is_a?(Array)
-
v.map{|inner_value| "#{k}=#{inner_value}"}.join(joiner)
-
else
-
"#{k}=#{v}"
-
end
-
}.join(joiner)
-
when ';'
-
return_value.map{|k,v|
-
if v.is_a?(Array) && v.first =~ /=/
-
';' + v.join(";")
-
elsif v.is_a?(Array)
-
';' + v.map{|inner_value| "#{k}=#{inner_value}"}.join(";")
-
else
-
v && v != '' ? ";#{k}=#{v}" : ";#{k}"
-
end
-
}.join
-
else
-
leader + return_value.map{|k,v| v}.join(joiner)
-
end
-
end
-
-
##
-
# Takes a set of values, and joins them together based on the
-
# operator.
-
#
-
# @param [Hash, Array, String] value
-
# Normalizes keys and values with IDNA#unicode_normalize_kc
-
#
-
# @return [Hash, Array, String] The normalized values
-
1
def normalize_value(value)
-
unless value.is_a?(Hash)
-
value = value.respond_to?(:to_ary) ? value.to_ary : value.to_str
-
end
-
-
# Handle unicode normalization
-
if value.kind_of?(Array)
-
value.map! { |val| Addressable::IDNA.unicode_normalize_kc(val) }
-
elsif value.kind_of?(Hash)
-
value = value.inject({}) { |acc, (k, v)|
-
acc[Addressable::IDNA.unicode_normalize_kc(k)] =
-
Addressable::IDNA.unicode_normalize_kc(v)
-
acc
-
}
-
else
-
value = Addressable::IDNA.unicode_normalize_kc(value)
-
end
-
value
-
end
-
-
##
-
# Generates a hash with string keys
-
#
-
# @param [Hash] mapping A mapping hash to normalize
-
#
-
# @return [Hash]
-
# A hash with stringified keys
-
1
def normalize_keys(mapping)
-
return mapping.inject({}) do |accu, pair|
-
name, value = pair
-
if Symbol === name
-
name = name.to_s
-
elsif name.respond_to?(:to_str)
-
name = name.to_str
-
else
-
raise TypeError,
-
"Can't convert #{name.class} into String."
-
end
-
accu[name] = value
-
accu
-
end
-
end
-
-
##
-
# Generates the <tt>Regexp</tt> that parses a template pattern.
-
#
-
# @param [String] pattern The URI template pattern.
-
# @param [#match] processor The template processor to use.
-
#
-
# @return [Regexp]
-
# A regular expression which may be used to parse a template pattern.
-
1
def parse_template_pattern(pattern, processor=nil)
-
# Escape the pattern. The two gsubs restore the escaped curly braces
-
# back to their original form. Basically, escape everything that isn't
-
# within an expansion.
-
escaped_pattern = Regexp.escape(
-
pattern
-
).gsub(/\\\{(.*?)\\\}/) do |escaped|
-
escaped.gsub(/\\(.)/, "\\1")
-
end
-
-
expansions = []
-
-
# Create a regular expression that captures the values of the
-
# variables in the URI.
-
regexp_string = escaped_pattern.gsub( EXPRESSION ) do |expansion|
-
-
expansions << expansion
-
_, operator, varlist = *expansion.match(EXPRESSION)
-
leader = Regexp.escape(LEADERS.fetch(operator, ''))
-
joiner = Regexp.escape(JOINERS.fetch(operator, ','))
-
combined = varlist.split(',').map do |varspec|
-
_, name, modifier = *varspec.match(VARSPEC)
-
-
result = processor && processor.respond_to?(:match) ? processor.match(name) : nil
-
if result
-
"(?<#{name}>#{ result })"
-
else
-
group = case operator
-
when '+'
-
"#{ RESERVED }*?"
-
when '#'
-
"#{ RESERVED }*?"
-
when '/'
-
"#{ UNRESERVED }*?"
-
when '.'
-
"#{ UNRESERVED.gsub('\.', '') }*?"
-
when ';'
-
"#{ UNRESERVED }*=?#{ UNRESERVED }*?"
-
when '?'
-
"#{ UNRESERVED }*=#{ UNRESERVED }*?"
-
when '&'
-
"#{ UNRESERVED }*=#{ UNRESERVED }*?"
-
else
-
"#{ UNRESERVED }*?"
-
end
-
if modifier == '*'
-
"(?<#{name}>#{group}(?:#{joiner}?#{group})*)?"
-
else
-
"(?<#{name}>#{group})?"
-
end
-
end
-
end.join("#{joiner}?")
-
"(?:|#{leader}#{combined})"
-
end
-
-
# Ensure that the regular expression matches the whole URI.
-
regexp_string = "^#{regexp_string}$"
-
return expansions, Regexp.new(regexp_string)
-
end
-
-
end
-
end
-
# encoding:utf-8
-
#--
-
# Copyright (C) 2006-2015 Bob Aman
-
#
-
# Licensed under the Apache License, Version 2.0 (the "License");
-
# you may not use this file except in compliance with the License.
-
# You may obtain a copy of the License at
-
#
-
# http://www.apache.org/licenses/LICENSE-2.0
-
#
-
# Unless required by applicable law or agreed to in writing, software
-
# distributed under the License is distributed on an "AS IS" BASIS,
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-
# See the License for the specific language governing permissions and
-
# limitations under the License.
-
#++
-
-
-
1
require "addressable/version"
-
1
require "addressable/idna"
-
-
##
-
# Addressable is a library for processing links and URIs.
-
1
module Addressable
-
##
-
# This is an implementation of a URI parser based on
-
# <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>,
-
# <a href="http://www.ietf.org/rfc/rfc3987.txt">RFC 3987</a>.
-
1
class URI
-
##
-
# Raised if something other than a uri is supplied.
-
1
class InvalidURIError < StandardError
-
end
-
-
##
-
# Container for the character classes specified in
-
# <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
-
1
module CharacterClasses
-
1
ALPHA = "a-zA-Z"
-
1
DIGIT = "0-9"
-
1
GEN_DELIMS = "\\:\\/\\?\\#\\[\\]\\@"
-
1
SUB_DELIMS = "\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\="
-
1
RESERVED = GEN_DELIMS + SUB_DELIMS
-
1
UNRESERVED = ALPHA + DIGIT + "\\-\\.\\_\\~"
-
1
PCHAR = UNRESERVED + SUB_DELIMS + "\\:\\@"
-
1
SCHEME = ALPHA + DIGIT + "\\-\\+\\."
-
1
HOST = ALPHA + DIGIT + "\\-\\.\\[\\:\\]"
-
1
AUTHORITY = PCHAR
-
1
PATH = PCHAR + "\\/"
-
1
QUERY = PCHAR + "\\/\\?"
-
1
FRAGMENT = PCHAR + "\\/\\?"
-
end
-
-
1
SLASH = '/'
-
1
EMPTY_STR = ''
-
-
1
URIREGEX = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/
-
-
1
PORT_MAPPING = {
-
"http" => 80,
-
"https" => 443,
-
"ftp" => 21,
-
"tftp" => 69,
-
"sftp" => 22,
-
"ssh" => 22,
-
"svn+ssh" => 22,
-
"telnet" => 23,
-
"nntp" => 119,
-
"gopher" => 70,
-
"wais" => 210,
-
"ldap" => 389,
-
"prospero" => 1525
-
}
-
-
##
-
# Returns a URI object based on the parsed string.
-
#
-
# @param [String, Addressable::URI, #to_str] uri
-
# The URI string to parse.
-
# No parsing is performed if the object is already an
-
# <code>Addressable::URI</code>.
-
#
-
# @return [Addressable::URI] The parsed URI.
-
1
def self.parse(uri)
-
# If we were given nil, return nil.
-
4
return nil unless uri
-
# If a URI object is passed, just return itself.
-
4
return uri.dup if uri.kind_of?(self)
-
-
# If a URI object of the Ruby standard library variety is passed,
-
# convert it to a string, then parse the string.
-
# We do the check this way because we don't want to accidentally
-
# cause a missing constant exception to be thrown.
-
4
if uri.class.name =~ /^URI\b/
-
uri = uri.to_s
-
end
-
-
# Otherwise, convert to a String
-
begin
-
uri = uri.to_str
-
rescue TypeError, NoMethodError
-
raise TypeError, "Can't convert #{uri.class} into String."
-
4
end if not uri.is_a? String
-
-
# This Regexp supplied as an example in RFC 3986, and it works great.
-
4
scan = uri.scan(URIREGEX)
-
4
fragments = scan[0]
-
4
scheme = fragments[1]
-
4
authority = fragments[3]
-
4
path = fragments[4]
-
4
query = fragments[6]
-
4
fragment = fragments[8]
-
4
user = nil
-
4
password = nil
-
4
host = nil
-
4
port = nil
-
4
if authority != nil
-
# The Regexp above doesn't split apart the authority.
-
4
userinfo = authority[/^([^\[\]]*)@/, 1]
-
4
if userinfo != nil
-
user = userinfo.strip[/^([^:]*):?/, 1]
-
password = userinfo.strip[/:(.*)$/, 1]
-
end
-
4
host = authority.gsub(
-
/^([^\[\]]*)@/, EMPTY_STR
-
).gsub(
-
/:([^:@\[\]]*?)$/, EMPTY_STR
-
)
-
4
port = authority[/:([^:@\[\]]*?)$/, 1]
-
end
-
4
if port == EMPTY_STR
-
port = nil
-
end
-
-
4
return new(
-
:scheme => scheme,
-
:user => user,
-
:password => password,
-
:host => host,
-
:port => port,
-
:path => path,
-
:query => query,
-
:fragment => fragment
-
)
-
end
-
-
##
-
# Converts an input to a URI. The input does not have to be a valid
-
# URI — the method will use heuristics to guess what URI was intended.
-
# This is not standards-compliant, merely user-friendly.
-
#
-
# @param [String, Addressable::URI, #to_str] uri
-
# The URI string to parse.
-
# No parsing is performed if the object is already an
-
# <code>Addressable::URI</code>.
-
# @param [Hash] hints
-
# A <code>Hash</code> of hints to the heuristic parser.
-
# Defaults to <code>{:scheme => "http"}</code>.
-
#
-
# @return [Addressable::URI] The parsed URI.
-
1
def self.heuristic_parse(uri, hints={})
-
# If we were given nil, return nil.
-
8
return nil unless uri
-
# If a URI object is passed, just return itself.
-
8
return uri.dup if uri.kind_of?(self)
-
-
# If a URI object of the Ruby standard library variety is passed,
-
# convert it to a string, then parse the string.
-
# We do the check this way because we don't want to accidentally
-
# cause a missing constant exception to be thrown.
-
4
if uri.class.name =~ /^URI\b/
-
uri = uri.to_s
-
end
-
-
4
if !uri.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{uri.class} into String."
-
end
-
# Otherwise, convert to a String
-
4
uri = uri.to_str.dup
-
4
hints = {
-
:scheme => "http"
-
}.merge(hints)
-
4
case uri
-
when /^http:\/+/
-
4
uri.gsub!(/^http:\/+/, "http://")
-
when /^https:\/+/
-
uri.gsub!(/^https:\/+/, "https://")
-
when /^feed:\/+http:\/+/
-
uri.gsub!(/^feed:\/+http:\/+/, "feed:http://")
-
when /^feed:\/+/
-
uri.gsub!(/^feed:\/+/, "feed://")
-
when /^file:\/+/
-
uri.gsub!(/^file:\/+/, "file:///")
-
when /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
-
uri.gsub!(/^/, hints[:scheme] + "://")
-
end
-
4
parsed = self.parse(uri)
-
4
if parsed.scheme =~ /^[^\/?#\.]+\.[^\/?#]+$/
-
parsed = self.parse(hints[:scheme] + "://" + uri)
-
end
-
4
if parsed.path.include?(".")
-
new_host = parsed.path[/^([^\/]+\.[^\/]*)/, 1]
-
if new_host
-
parsed.defer_validation do
-
new_path = parsed.path.gsub(
-
Regexp.new("^" + Regexp.escape(new_host)), EMPTY_STR)
-
parsed.host = new_host
-
parsed.path = new_path
-
parsed.scheme = hints[:scheme] unless parsed.scheme
-
end
-
end
-
end
-
4
return parsed
-
end
-
-
##
-
# Converts a path to a file scheme URI. If the path supplied is
-
# relative, it will be returned as a relative URI. If the path supplied
-
# is actually a non-file URI, it will parse the URI as if it had been
-
# parsed with <code>Addressable::URI.parse</code>. Handles all of the
-
# various Microsoft-specific formats for specifying paths.
-
#
-
# @param [String, Addressable::URI, #to_str] path
-
# Typically a <code>String</code> path to a file or directory, but
-
# will return a sensible return value if an absolute URI is supplied
-
# instead.
-
#
-
# @return [Addressable::URI]
-
# The parsed file scheme URI or the original URI if some other URI
-
# scheme was provided.
-
#
-
# @example
-
# base = Addressable::URI.convert_path("/absolute/path/")
-
# uri = Addressable::URI.convert_path("relative/path")
-
# (base + uri).to_s
-
# #=> "file:///absolute/path/relative/path"
-
#
-
# Addressable::URI.convert_path(
-
# "c:\\windows\\My Documents 100%20\\foo.txt"
-
# ).to_s
-
# #=> "file:///c:/windows/My%20Documents%20100%20/foo.txt"
-
#
-
# Addressable::URI.convert_path("http://example.com/").to_s
-
# #=> "http://example.com/"
-
1
def self.convert_path(path)
-
# If we were given nil, return nil.
-
return nil unless path
-
# If a URI object is passed, just return itself.
-
return path if path.kind_of?(self)
-
if !path.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{path.class} into String."
-
end
-
# Otherwise, convert to a String
-
path = path.to_str.strip
-
-
path.gsub!(/^file:\/?\/?/, EMPTY_STR) if path =~ /^file:\/?\/?/
-
path = SLASH + path if path =~ /^([a-zA-Z])[\|:]/
-
uri = self.parse(path)
-
-
if uri.scheme == nil
-
# Adjust windows-style uris
-
uri.path.gsub!(/^\/?([a-zA-Z])[\|:][\\\/]/) do
-
"/#{$1.downcase}:/"
-
end
-
uri.path.gsub!(/\\/, SLASH)
-
if File.exist?(uri.path) &&
-
File.stat(uri.path).directory?
-
uri.path.gsub!(/\/$/, EMPTY_STR)
-
uri.path = uri.path + '/'
-
end
-
-
# If the path is absolute, set the scheme and host.
-
if uri.path =~ /^\//
-
uri.scheme = "file"
-
uri.host = EMPTY_STR
-
end
-
uri.normalize!
-
end
-
-
return uri
-
end
-
-
##
-
# Joins several URIs together.
-
#
-
# @param [String, Addressable::URI, #to_str] *uris
-
# The URIs to join.
-
#
-
# @return [Addressable::URI] The joined URI.
-
#
-
# @example
-
# base = "http://example.com/"
-
# uri = Addressable::URI.parse("relative/path")
-
# Addressable::URI.join(base, uri)
-
# #=> #<Addressable::URI:0xcab390 URI:http://example.com/relative/path>
-
1
def self.join(*uris)
-
uri_objects = uris.collect do |uri|
-
if !uri.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{uri.class} into String."
-
end
-
uri.kind_of?(self) ? uri : self.parse(uri.to_str)
-
end
-
result = uri_objects.shift.dup
-
for uri in uri_objects
-
result.join!(uri)
-
end
-
return result
-
end
-
-
##
-
# Percent encodes a URI component.
-
#
-
# @param [String, #to_str] component The URI component to encode.
-
#
-
# @param [String, Regexp] character_class
-
# The characters which are not percent encoded. If a <code>String</code>
-
# is passed, the <code>String</code> must be formatted as a regular
-
# expression character class. (Do not include the surrounding square
-
# brackets.) For example, <code>"b-zB-Z0-9"</code> would cause
-
# everything but the letters 'b' through 'z' and the numbers '0' through
-
# '9' to be percent encoded. If a <code>Regexp</code> is passed, the
-
# value <code>/[^b-zB-Z0-9]/</code> would have the same effect. A set of
-
# useful <code>String</code> values may be found in the
-
# <code>Addressable::URI::CharacterClasses</code> module. The default
-
# value is the reserved plus unreserved character classes specified in
-
# <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
-
#
-
# @param [Regexp] upcase_encoded
-
# A string of characters that may already be percent encoded, and whose
-
# encodings should be upcased. This allows normalization of percent
-
# encodings for characters not included in the
-
# <code>character_class</code>.
-
#
-
# @return [String] The encoded component.
-
#
-
# @example
-
# Addressable::URI.encode_component("simple/example", "b-zB-Z0-9")
-
# => "simple%2Fex%61mple"
-
# Addressable::URI.encode_component("simple/example", /[^b-zB-Z0-9]/)
-
# => "simple%2Fex%61mple"
-
# Addressable::URI.encode_component(
-
# "simple/example", Addressable::URI::CharacterClasses::UNRESERVED
-
# )
-
# => "simple%2Fexample"
-
1
def self.encode_component(component, character_class=
-
CharacterClasses::RESERVED + CharacterClasses::UNRESERVED,
-
upcase_encoded='')
-
232
return nil if component.nil?
-
-
begin
-
if component.kind_of?(Symbol) ||
-
component.kind_of?(Numeric) ||
-
component.kind_of?(TrueClass) ||
-
component.kind_of?(FalseClass)
-
component = component.to_s
-
else
-
component = component.to_str
-
end
-
rescue TypeError, NoMethodError
-
raise TypeError, "Can't convert #{component.class} into String."
-
232
end if !component.is_a? String
-
-
232
if ![String, Regexp].include?(character_class.class)
-
raise TypeError,
-
"Expected String or Regexp, got #{character_class.inspect}"
-
end
-
232
if character_class.kind_of?(String)
-
68
character_class = /[^#{character_class}]/
-
end
-
232
if component.respond_to?(:force_encoding)
-
# We can't perform regexps on invalid UTF sequences, but
-
# here we need to, so switch to ASCII.
-
232
component = component.dup
-
232
component.force_encoding(Encoding::ASCII_8BIT)
-
end
-
# Avoiding gsub! because there are edge cases with frozen strings
-
232
component = component.gsub(character_class) do |sequence|
-
4
(sequence.unpack('C*').map { |c| "%" + ("%02x" % c).upcase }).join
-
end
-
232
if upcase_encoded.length > 0
-
68
component = component.gsub(/%(#{upcase_encoded.chars.map do |char|
-
136
char.unpack('C*').map { |c| '%02x' % c }.join
-
end.join('|')})/i) { |s| s.upcase }
-
end
-
232
return component
-
end
-
-
1
class << self
-
1
alias_method :encode_component, :encode_component
-
end
-
-
##
-
# Unencodes any percent encoded characters within a URI component.
-
# This method may be used for unencoding either components or full URIs,
-
# however, it is recommended to use the <code>unencode_component</code>
-
# alias when unencoding components.
-
#
-
# @param [String, Addressable::URI, #to_str] uri
-
# The URI or component to unencode.
-
#
-
# @param [Class] return_type
-
# The type of object to return.
-
# This value may only be set to <code>String</code> or
-
# <code>Addressable::URI</code>. All other values are invalid. Defaults
-
# to <code>String</code>.
-
#
-
# @param [String] leave_encoded
-
# A string of characters to leave encoded. If a percent encoded character
-
# in this list is encountered then it will remain percent encoded.
-
#
-
# @return [String, Addressable::URI]
-
# The unencoded component or URI.
-
# The return type is determined by the <code>return_type</code>
-
# parameter.
-
1
def self.unencode(uri, return_type=String, leave_encoded='')
-
328
return nil if uri.nil?
-
-
begin
-
8
uri = uri.to_str
-
rescue NoMethodError, TypeError
-
raise TypeError, "Can't convert #{uri.class} into String."
-
328
end if !uri.is_a? String
-
328
if ![String, ::Addressable::URI].include?(return_type)
-
raise TypeError,
-
"Expected Class (String or Addressable::URI), " +
-
"got #{return_type.inspect}"
-
end
-
328
uri = uri.dup
-
# Seriously, only use UTF-8. I'm really not kidding!
-
328
uri.force_encoding("utf-8") if uri.respond_to?(:force_encoding)
-
328
leave_encoded.force_encoding("utf-8") if leave_encoded.respond_to?(:force_encoding)
-
328
result = uri.gsub(/%[0-9a-f]{2}/iu) do |sequence|
-
4
c = sequence[1..3].to_i(16).chr
-
4
c.force_encoding("utf-8") if c.respond_to?(:force_encoding)
-
4
leave_encoded.include?(c) ? sequence : c
-
end
-
328
result.force_encoding("utf-8") if result.respond_to?(:force_encoding)
-
328
if return_type == String
-
328
return result
-
elsif return_type == ::Addressable::URI
-
return ::Addressable::URI.parse(result)
-
end
-
end
-
-
1
class << self
-
1
alias_method :unescape, :unencode
-
1
alias_method :unencode_component, :unencode
-
1
alias_method :unescape_component, :unencode
-
end
-
-
-
##
-
# Normalizes the encoding of a URI component.
-
#
-
# @param [String, #to_str] component The URI component to encode.
-
#
-
# @param [String, Regexp] character_class
-
# The characters which are not percent encoded. If a <code>String</code>
-
# is passed, the <code>String</code> must be formatted as a regular
-
# expression character class. (Do not include the surrounding square
-
# brackets.) For example, <code>"b-zB-Z0-9"</code> would cause
-
# everything but the letters 'b' through 'z' and the numbers '0'
-
# through '9' to be percent encoded. If a <code>Regexp</code> is passed,
-
# the value <code>/[^b-zB-Z0-9]/</code> would have the same effect. A
-
# set of useful <code>String</code> values may be found in the
-
# <code>Addressable::URI::CharacterClasses</code> module. The default
-
# value is the reserved plus unreserved character classes specified in
-
# <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
-
#
-
# @param [String] leave_encoded
-
# When <code>character_class</code> is a <code>String</code> then
-
# <code>leave_encoded</code> is a string of characters that should remain
-
# percent encoded while normalizing the component; if they appear percent
-
# encoded in the original component, then they will be upcased ("%2f"
-
# normalized to "%2F") but otherwise left alone.
-
#
-
# @return [String] The normalized component.
-
#
-
# @example
-
# Addressable::URI.normalize_component("simpl%65/%65xampl%65", "b-zB-Z")
-
# => "simple%2Fex%61mple"
-
# Addressable::URI.normalize_component(
-
# "simpl%65/%65xampl%65", /[^b-zB-Z]/
-
# )
-
# => "simple%2Fex%61mple"
-
# Addressable::URI.normalize_component(
-
# "simpl%65/%65xampl%65",
-
# Addressable::URI::CharacterClasses::UNRESERVED
-
# )
-
# => "simple%2Fexample"
-
# Addressable::URI.normalize_component(
-
# "one%20two%2fthree%26four",
-
# "0-9a-zA-Z &/",
-
# "/"
-
# )
-
# => "one two%2Fthree&four"
-
1
def self.normalize_component(component, character_class=
-
CharacterClasses::RESERVED + CharacterClasses::UNRESERVED,
-
leave_encoded='')
-
164
return nil if component.nil?
-
-
begin
-
component = component.to_str
-
rescue NoMethodError, TypeError
-
raise TypeError, "Can't convert #{component.class} into String."
-
164
end if !component.is_a? String
-
-
164
if ![String, Regexp].include?(character_class.class)
-
raise TypeError,
-
"Expected String or Regexp, got #{character_class.inspect}"
-
end
-
164
if character_class.kind_of?(String)
-
164
leave_re = if leave_encoded.length > 0
-
68
character_class = "#{character_class}%" unless character_class.include?('%')
-
-
68
"|%(?!#{leave_encoded.chars.map do |char|
-
136
seq = char.unpack('C*').map { |c| '%02x' % c }.join
-
68
[seq.upcase, seq.downcase]
-
end.flatten.join('|')})"
-
end
-
-
164
character_class = /[^#{character_class}]#{leave_re}/
-
end
-
164
if component.respond_to?(:force_encoding)
-
# We can't perform regexps on invalid UTF sequences, but
-
# here we need to, so switch to ASCII.
-
164
component = component.dup
-
164
component.force_encoding(Encoding::ASCII_8BIT)
-
end
-
164
unencoded = self.unencode_component(component, String, leave_encoded)
-
164
begin
-
164
encoded = self.encode_component(
-
Addressable::IDNA.unicode_normalize_kc(unencoded),
-
character_class,
-
leave_encoded
-
)
-
rescue ArgumentError
-
encoded = self.encode_component(unencoded)
-
end
-
164
if encoded.respond_to?(:force_encoding)
-
164
encoded.force_encoding(Encoding::UTF_8)
-
end
-
164
return encoded
-
end
-
-
##
-
# Percent encodes any special characters in the URI.
-
#
-
# @param [String, Addressable::URI, #to_str] uri
-
# The URI to encode.
-
#
-
# @param [Class] return_type
-
# The type of object to return.
-
# This value may only be set to <code>String</code> or
-
# <code>Addressable::URI</code>. All other values are invalid. Defaults
-
# to <code>String</code>.
-
#
-
# @return [String, Addressable::URI]
-
# The encoded URI.
-
# The return type is determined by the <code>return_type</code>
-
# parameter.
-
1
def self.encode(uri, return_type=String)
-
return nil if uri.nil?
-
-
begin
-
uri = uri.to_str
-
rescue NoMethodError, TypeError
-
raise TypeError, "Can't convert #{uri.class} into String."
-
end if !uri.is_a? String
-
-
if ![String, ::Addressable::URI].include?(return_type)
-
raise TypeError,
-
"Expected Class (String or Addressable::URI), " +
-
"got #{return_type.inspect}"
-
end
-
uri_object = uri.kind_of?(self) ? uri : self.parse(uri)
-
encoded_uri = Addressable::URI.new(
-
:scheme => self.encode_component(uri_object.scheme,
-
Addressable::URI::CharacterClasses::SCHEME),
-
:authority => self.encode_component(uri_object.authority,
-
Addressable::URI::CharacterClasses::AUTHORITY),
-
:path => self.encode_component(uri_object.path,
-
Addressable::URI::CharacterClasses::PATH),
-
:query => self.encode_component(uri_object.query,
-
Addressable::URI::CharacterClasses::QUERY),
-
:fragment => self.encode_component(uri_object.fragment,
-
Addressable::URI::CharacterClasses::FRAGMENT)
-
)
-
if return_type == String
-
return encoded_uri.to_s
-
elsif return_type == ::Addressable::URI
-
return encoded_uri
-
end
-
end
-
-
1
class << self
-
1
alias_method :escape, :encode
-
end
-
-
##
-
# Normalizes the encoding of a URI. Characters within a hostname are
-
# not percent encoded to allow for internationalized domain names.
-
#
-
# @param [String, Addressable::URI, #to_str] uri
-
# The URI to encode.
-
#
-
# @param [Class] return_type
-
# The type of object to return.
-
# This value may only be set to <code>String</code> or
-
# <code>Addressable::URI</code>. All other values are invalid. Defaults
-
# to <code>String</code>.
-
#
-
# @return [String, Addressable::URI]
-
# The encoded URI.
-
# The return type is determined by the <code>return_type</code>
-
# parameter.
-
1
def self.normalized_encode(uri, return_type=String)
-
begin
-
uri = uri.to_str
-
rescue NoMethodError, TypeError
-
raise TypeError, "Can't convert #{uri.class} into String."
-
end if !uri.is_a? String
-
-
if ![String, ::Addressable::URI].include?(return_type)
-
raise TypeError,
-
"Expected Class (String or Addressable::URI), " +
-
"got #{return_type.inspect}"
-
end
-
uri_object = uri.kind_of?(self) ? uri : self.parse(uri)
-
components = {
-
:scheme => self.unencode_component(uri_object.scheme),
-
:user => self.unencode_component(uri_object.user),
-
:password => self.unencode_component(uri_object.password),
-
:host => self.unencode_component(uri_object.host),
-
:port => (uri_object.port.nil? ? nil : uri_object.port.to_s),
-
:path => self.unencode_component(uri_object.path),
-
:query => self.unencode_component(uri_object.query),
-
:fragment => self.unencode_component(uri_object.fragment)
-
}
-
components.each do |key, value|
-
if value != nil
-
begin
-
components[key] =
-
Addressable::IDNA.unicode_normalize_kc(value.to_str)
-
rescue ArgumentError
-
# Likely a malformed UTF-8 character, skip unicode normalization
-
components[key] = value.to_str
-
end
-
end
-
end
-
encoded_uri = Addressable::URI.new(
-
:scheme => self.encode_component(components[:scheme],
-
Addressable::URI::CharacterClasses::SCHEME),
-
:user => self.encode_component(components[:user],
-
Addressable::URI::CharacterClasses::UNRESERVED),
-
:password => self.encode_component(components[:password],
-
Addressable::URI::CharacterClasses::UNRESERVED),
-
:host => components[:host],
-
:port => components[:port],
-
:path => self.encode_component(components[:path],
-
Addressable::URI::CharacterClasses::PATH),
-
:query => self.encode_component(components[:query],
-
Addressable::URI::CharacterClasses::QUERY),
-
:fragment => self.encode_component(components[:fragment],
-
Addressable::URI::CharacterClasses::FRAGMENT)
-
)
-
if return_type == String
-
return encoded_uri.to_s
-
elsif return_type == ::Addressable::URI
-
return encoded_uri
-
end
-
end
-
-
##
-
# Encodes a set of key/value pairs according to the rules for the
-
# <code>application/x-www-form-urlencoded</code> MIME type.
-
#
-
# @param [#to_hash, #to_ary] form_values
-
# The form values to encode.
-
#
-
# @param [TrueClass, FalseClass] sort
-
# Sort the key/value pairs prior to encoding.
-
# Defaults to <code>false</code>.
-
#
-
# @return [String]
-
# The encoded value.
-
1
def self.form_encode(form_values, sort=false)
-
if form_values.respond_to?(:to_hash)
-
form_values = form_values.to_hash.to_a
-
elsif form_values.respond_to?(:to_ary)
-
form_values = form_values.to_ary
-
else
-
raise TypeError, "Can't convert #{form_values.class} into Array."
-
end
-
-
form_values = form_values.inject([]) do |accu, (key, value)|
-
if value.kind_of?(Array)
-
value.each do |v|
-
accu << [key.to_s, v.to_s]
-
end
-
else
-
accu << [key.to_s, value.to_s]
-
end
-
accu
-
end
-
-
if sort
-
# Useful for OAuth and optimizing caching systems
-
form_values = form_values.sort
-
end
-
escaped_form_values = form_values.map do |(key, value)|
-
# Line breaks are CRLF pairs
-
[
-
self.encode_component(
-
key.gsub(/(\r\n|\n|\r)/, "\r\n"),
-
CharacterClasses::UNRESERVED
-
).gsub("%20", "+"),
-
self.encode_component(
-
value.gsub(/(\r\n|\n|\r)/, "\r\n"),
-
CharacterClasses::UNRESERVED
-
).gsub("%20", "+")
-
]
-
end
-
return escaped_form_values.map do |(key, value)|
-
"#{key}=#{value}"
-
end.join("&")
-
end
-
-
##
-
# Decodes a <code>String</code> according to the rules for the
-
# <code>application/x-www-form-urlencoded</code> MIME type.
-
#
-
# @param [String, #to_str] encoded_value
-
# The form values to decode.
-
#
-
# @return [Array]
-
# The decoded values.
-
# This is not a <code>Hash</code> because of the possibility for
-
# duplicate keys.
-
1
def self.form_unencode(encoded_value)
-
if !encoded_value.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{encoded_value.class} into String."
-
end
-
encoded_value = encoded_value.to_str
-
split_values = encoded_value.split("&").map do |pair|
-
pair.split("=", 2)
-
end
-
return split_values.map do |(key, value)|
-
[
-
key ? self.unencode_component(
-
key.gsub("+", "%20")).gsub(/(\r\n|\n|\r)/, "\n") : nil,
-
value ? (self.unencode_component(
-
value.gsub("+", "%20")).gsub(/(\r\n|\n|\r)/, "\n")) : nil
-
]
-
end
-
end
-
-
##
-
# Creates a new uri object from component parts.
-
#
-
# @option [String, #to_str] scheme The scheme component.
-
# @option [String, #to_str] user The user component.
-
# @option [String, #to_str] password The password component.
-
# @option [String, #to_str] userinfo
-
# The userinfo component. If this is supplied, the user and password
-
# components must be omitted.
-
# @option [String, #to_str] host The host component.
-
# @option [String, #to_str] port The port component.
-
# @option [String, #to_str] authority
-
# The authority component. If this is supplied, the user, password,
-
# userinfo, host, and port components must be omitted.
-
# @option [String, #to_str] path The path component.
-
# @option [String, #to_str] query The query component.
-
# @option [String, #to_str] fragment The fragment component.
-
#
-
# @return [Addressable::URI] The constructed URI object.
-
1
def initialize(options={})
-
44
if options.has_key?(:authority)
-
8
if (options.keys & [:userinfo, :user, :password, :host, :port]).any?
-
raise ArgumentError,
-
"Cannot specify both an authority and any of the components " +
-
"within the authority."
-
end
-
end
-
44
if options.has_key?(:userinfo)
-
if (options.keys & [:user, :password]).any?
-
raise ArgumentError,
-
"Cannot specify both a userinfo and either the user or password."
-
end
-
end
-
-
44
self.defer_validation do
-
# Bunch of crazy logic required because of the composite components
-
# like userinfo and authority.
-
44
self.scheme = options[:scheme] if options[:scheme]
-
44
self.user = options[:user] if options[:user]
-
44
self.password = options[:password] if options[:password]
-
44
self.userinfo = options[:userinfo] if options[:userinfo]
-
44
self.host = options[:host] if options[:host]
-
44
self.port = options[:port] if options[:port]
-
44
self.authority = options[:authority] if options[:authority]
-
44
self.path = options[:path] if options[:path]
-
44
self.query = options[:query] if options[:query]
-
44
self.query_values = options[:query_values] if options[:query_values]
-
44
self.fragment = options[:fragment] if options[:fragment]
-
end
-
44
self.to_s
-
end
-
-
##
-
# Freeze URI, initializing instance variables.
-
#
-
# @return [Addressable::URI] The frozen URI object.
-
1
def freeze
-
8
self.normalized_scheme
-
8
self.normalized_user
-
8
self.normalized_password
-
8
self.normalized_userinfo
-
8
self.normalized_host
-
8
self.normalized_port
-
8
self.normalized_authority
-
8
self.normalized_site
-
8
self.normalized_path
-
8
self.normalized_query
-
8
self.normalized_fragment
-
8
self.hash
-
8
super
-
end
-
-
##
-
# The scheme component for this URI.
-
#
-
# @return [String] The scheme component.
-
1
def scheme
-
571
return defined?(@scheme) ? @scheme : nil
-
end
-
-
##
-
# The scheme component for this URI, normalized.
-
#
-
# @return [String] The scheme component, normalized.
-
1
def normalized_scheme
-
56
return nil unless self.scheme
-
@normalized_scheme ||= begin
-
16
if self.scheme =~ /^\s*ssh\+svn\s*$/i
-
"svn+ssh"
-
else
-
16
Addressable::URI.normalize_component(
-
self.scheme.strip.downcase,
-
Addressable::URI::CharacterClasses::SCHEME
-
)
-
end
-
56
end
-
end
-
-
##
-
# Sets the scheme component for this URI.
-
#
-
# @param [String, #to_str] new_scheme The new scheme component.
-
1
def scheme=(new_scheme)
-
44
if new_scheme && !new_scheme.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_scheme.class} into String."
-
elsif new_scheme
-
44
new_scheme = new_scheme.to_str
-
end
-
44
if new_scheme && new_scheme !~ /\A[a-z][a-z0-9\.\+\-]*\z/i
-
raise InvalidURIError, "Invalid scheme format: #{new_scheme}"
-
end
-
44
@scheme = new_scheme
-
44
@scheme = nil if @scheme.to_s.strip.empty?
-
-
# Reset dependent values
-
44
remove_instance_variable(:@normalized_scheme) if defined?(@normalized_scheme)
-
44
remove_composite_values
-
-
# Ensure we haven't created an invalid URI
-
44
validate()
-
end
-
-
##
-
# The user component for this URI.
-
#
-
# @return [String] The user component.
-
1
def user
-
108
return defined?(@user) ? @user : nil
-
end
-
-
##
-
# The user component for this URI, normalized.
-
#
-
# @return [String] The user component, normalized.
-
1
def normalized_user
-
8
return nil unless self.user
-
return @normalized_user if defined?(@normalized_user)
-
@normalized_user ||= begin
-
if normalized_scheme =~ /https?/ && self.user.strip.empty? &&
-
(!self.password || self.password.strip.empty?)
-
nil
-
else
-
Addressable::URI.normalize_component(
-
self.user.strip,
-
Addressable::URI::CharacterClasses::UNRESERVED
-
)
-
end
-
end
-
end
-
-
##
-
# Sets the user component for this URI.
-
#
-
# @param [String, #to_str] new_user The new user component.
-
1
def user=(new_user)
-
8
if new_user && !new_user.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_user.class} into String."
-
end
-
8
@user = new_user ? new_user.to_str : nil
-
-
# You can't have a nil user with a non-nil password
-
8
if password != nil
-
@user = EMPTY_STR if @user.nil?
-
end
-
-
# Reset dependent values
-
8
remove_instance_variable(:@userinfo) if defined?(@userinfo)
-
8
remove_instance_variable(:@normalized_userinfo) if defined?(@normalized_userinfo)
-
8
remove_instance_variable(:@authority) if defined?(@authority)
-
8
remove_instance_variable(:@normalized_user) if defined?(@normalized_user)
-
8
remove_composite_values
-
-
# Ensure we haven't created an invalid URI
-
8
validate()
-
end
-
-
##
-
# The password component for this URI.
-
#
-
# @return [String] The password component.
-
1
def password
-
116
return defined?(@password) ? @password : nil
-
end
-
-
##
-
# The password component for this URI, normalized.
-
#
-
# @return [String] The password component, normalized.
-
1
def normalized_password
-
8
return nil unless self.password
-
return @normalized_password if defined?(@normalized_password)
-
@normalized_password ||= begin
-
if self.normalized_scheme =~ /https?/ && self.password.strip.empty? &&
-
(!self.user || self.user.strip.empty?)
-
nil
-
else
-
Addressable::URI.normalize_component(
-
self.password.strip,
-
Addressable::URI::CharacterClasses::UNRESERVED
-
)
-
end
-
end
-
end
-
-
##
-
# Sets the password component for this URI.
-
#
-
# @param [String, #to_str] new_password The new password component.
-
1
def password=(new_password)
-
8
if new_password && !new_password.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_password.class} into String."
-
end
-
8
@password = new_password ? new_password.to_str : nil
-
-
# You can't have a nil user with a non-nil password
-
8
@password ||= nil
-
8
@user ||= nil
-
8
if @password != nil
-
@user = EMPTY_STR if @user.nil?
-
end
-
-
# Reset dependent values
-
8
remove_instance_variable(:@userinfo) if defined?(@userinfo)
-
8
remove_instance_variable(:@normalized_userinfo) if defined?(@normalized_userinfo)
-
8
remove_instance_variable(:@authority) if defined?(@authority)
-
8
remove_instance_variable(:@normalized_password) if defined?(@normalized_password)
-
8
remove_composite_values
-
-
# Ensure we haven't created an invalid URI
-
8
validate()
-
end
-
-
##
-
# The userinfo component for this URI.
-
# Combines the user and password components.
-
#
-
# @return [String] The userinfo component.
-
1
def userinfo
-
68
current_user = self.user
-
68
current_password = self.password
-
68
(current_user || current_password) && @userinfo ||= begin
-
if current_user && current_password
-
"#{current_user}:#{current_password}"
-
elsif current_user && !current_password
-
"#{current_user}"
-
end
-
68
end
-
end
-
-
##
-
# The userinfo component for this URI, normalized.
-
#
-
# @return [String] The userinfo component, normalized.
-
1
def normalized_userinfo
-
24
return nil unless self.userinfo
-
return @normalized_userinfo if defined?(@normalized_userinfo)
-
@normalized_userinfo ||= begin
-
current_user = self.normalized_user
-
current_password = self.normalized_password
-
if !current_user && !current_password
-
nil
-
elsif current_user && current_password
-
"#{current_user}:#{current_password}"
-
elsif current_user && !current_password
-
"#{current_user}"
-
end
-
end
-
end
-
-
##
-
# Sets the userinfo component for this URI.
-
#
-
# @param [String, #to_str] new_userinfo The new userinfo component.
-
1
def userinfo=(new_userinfo)
-
if new_userinfo && !new_userinfo.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_userinfo.class} into String."
-
end
-
new_user, new_password = if new_userinfo
-
[
-
new_userinfo.to_str.strip[/^(.*):/, 1],
-
new_userinfo.to_str.strip[/:(.*)$/, 1]
-
]
-
else
-
[nil, nil]
-
end
-
-
# Password assigned first to ensure validity in case of nil
-
self.password = new_password
-
self.user = new_user
-
-
# Reset dependent values
-
remove_instance_variable(:@authority) if defined?(@authority)
-
remove_composite_values
-
-
# Ensure we haven't created an invalid URI
-
validate()
-
end
-
-
##
-
# The host component for this URI.
-
#
-
# @return [String] The host component.
-
1
def host
-
456
return defined?(@host) ? @host : nil
-
end
-
-
##
-
# The host component for this URI, normalized.
-
#
-
# @return [String] The host component, normalized.
-
1
def normalized_host
-
24
return nil unless self.host
-
@normalized_host ||= begin
-
16
if !self.host.strip.empty?
-
16
result = ::Addressable::IDNA.to_ascii(
-
URI.unencode_component(self.host.strip.downcase)
-
)
-
16
if result =~ /[^\.]\.$/
-
# Single trailing dots are unnecessary.
-
result = result[0...-1]
-
end
-
16
result = Addressable::URI.normalize_component(
-
result,
-
CharacterClasses::HOST)
-
16
result
-
else
-
EMPTY_STR
-
end
-
24
end
-
end
-
-
##
-
# Sets the host component for this URI.
-
#
-
# @param [String, #to_str] new_host The new host component.
-
1
def host=(new_host)
-
44
if new_host && !new_host.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_host.class} into String."
-
end
-
44
@host = new_host ? new_host.to_str : nil
-
-
44
unreserved = CharacterClasses::UNRESERVED
-
44
sub_delims = CharacterClasses::SUB_DELIMS
-
if !@host.nil? && (@host =~ /[<>{}\/\?\#\@"[[:space:]]]/ ||
-
(@host[/^\[(.*)\]$/, 1] != nil && @host[/^\[(.*)\]$/, 1] !~
-
44
Regexp.new("^[#{unreserved}#{sub_delims}:]*$")))
-
raise InvalidURIError, "Invalid character in host: '#{@host.to_s}'"
-
end
-
-
# Reset dependent values
-
44
remove_instance_variable(:@authority) if defined?(@authority)
-
44
remove_instance_variable(:@normalized_host) if defined?(@normalized_host)
-
44
remove_composite_values
-
-
# Ensure we haven't created an invalid URI
-
44
validate()
-
end
-
-
##
-
# This method is same as URI::Generic#host except
-
# brackets for IPv6 (and 'IPvFuture') addresses are removed.
-
#
-
# @see Addressable::URI#host
-
#
-
# @return [String] The hostname for this URI.
-
1
def hostname
-
v = self.host
-
/\A\[(.*)\]\z/ =~ v ? $1 : v
-
end
-
-
##
-
# This method is same as URI::Generic#host= except
-
# the argument can be a bare IPv6 address (or 'IPvFuture').
-
#
-
# @see Addressable::URI#host=
-
#
-
# @param [String, #to_str] new_hostname The new hostname for this URI.
-
1
def hostname=(new_hostname)
-
if new_hostname &&
-
(new_hostname.respond_to?(:ipv4?) || new_hostname.respond_to?(:ipv6?))
-
new_hostname = new_hostname.to_s
-
elsif new_hostname && !new_hostname.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_hostname.class} into String."
-
end
-
v = new_hostname ? new_hostname.to_str : nil
-
v = "[#{v}]" if /\A\[.*\]\z/ !~ v && /:/ =~ v
-
self.host = v
-
end
-
-
##
-
# The authority component for this URI.
-
# Combines the user, password, host, and port components.
-
#
-
# @return [String] The authority component.
-
1
def authority
-
self.host && @authority ||= begin
-
44
authority = ""
-
44
if self.userinfo != nil
-
authority << "#{self.userinfo}@"
-
end
-
44
authority << self.host
-
44
if self.port != nil
-
36
authority << ":#{self.port}"
-
end
-
44
authority
-
136
end
-
end
-
-
##
-
# The authority component for this URI, normalized.
-
#
-
# @return [String] The authority component, normalized.
-
1
def normalized_authority
-
32
return nil unless self.authority
-
@normalized_authority ||= begin
-
16
authority = ""
-
16
if self.normalized_userinfo != nil
-
authority << "#{self.normalized_userinfo}@"
-
end
-
16
authority << self.normalized_host
-
16
if self.normalized_port != nil
-
authority << ":#{self.normalized_port}"
-
end
-
16
authority
-
32
end
-
end
-
-
##
-
# Sets the authority component for this URI.
-
#
-
# @param [String, #to_str] new_authority The new authority component.
-
1
def authority=(new_authority)
-
8
if new_authority
-
8
if !new_authority.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_authority.class} into String."
-
end
-
8
new_authority = new_authority.to_str
-
8
new_userinfo = new_authority[/^([^\[\]]*)@/, 1]
-
8
if new_userinfo
-
new_user = new_userinfo.strip[/^([^:]*):?/, 1]
-
new_password = new_userinfo.strip[/:(.*)$/, 1]
-
end
-
8
new_host = new_authority.gsub(
-
/^([^\[\]]*)@/, EMPTY_STR
-
).gsub(
-
/:([^:@\[\]]*?)$/, EMPTY_STR
-
)
-
8
new_port =
-
new_authority[/:([^:@\[\]]*?)$/, 1]
-
end
-
-
# Password assigned first to ensure validity in case of nil
-
8
self.password = defined?(new_password) ? new_password : nil
-
8
self.user = defined?(new_user) ? new_user : nil
-
8
self.host = defined?(new_host) ? new_host : nil
-
8
self.port = defined?(new_port) ? new_port : nil
-
-
# Reset dependent values
-
8
remove_instance_variable(:@userinfo) if defined?(@userinfo)
-
8
remove_instance_variable(:@normalized_userinfo) if defined?(@normalized_userinfo)
-
8
remove_composite_values
-
-
# Ensure we haven't created an invalid URI
-
8
validate()
-
end
-
-
##
-
# The origin for this URI, serialized to ASCII, as per
-
# RFC 6454, section 6.2.
-
#
-
# @return [String] The serialized origin.
-
1
def origin
-
if self.scheme && self.authority
-
if self.normalized_port
-
"#{self.normalized_scheme}://#{self.normalized_host}" +
-
":#{self.normalized_port}"
-
else
-
"#{self.normalized_scheme}://#{self.normalized_host}"
-
end
-
else
-
"null"
-
end
-
end
-
-
##
-
# Sets the origin for this URI, serialized to ASCII, as per
-
# RFC 6454, section 6.2. This assignment will reset the `userinfo`
-
# component.
-
#
-
# @param [String, #to_str] new_origin The new origin component.
-
1
def origin=(new_origin)
-
if new_origin
-
if !new_origin.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_origin.class} into String."
-
end
-
new_origin = new_origin.to_str
-
new_scheme = new_origin[/^([^:\/?#]+):\/\//, 1]
-
unless new_scheme
-
raise InvalidURIError, 'An origin cannot omit the scheme.'
-
end
-
new_host = new_origin[/:\/\/([^\/?#:]+)/, 1]
-
unless new_host
-
raise InvalidURIError, 'An origin cannot omit the host.'
-
end
-
new_port = new_origin[/:([^:@\[\]\/]*?)$/, 1]
-
end
-
-
self.scheme = defined?(new_scheme) ? new_scheme : nil
-
self.host = defined?(new_host) ? new_host : nil
-
self.port = defined?(new_port) ? new_port : nil
-
self.userinfo = nil
-
-
# Reset dependent values
-
remove_instance_variable(:@userinfo) if defined?(@userinfo)
-
remove_instance_variable(:@normalized_userinfo) if defined?(@normalized_userinfo)
-
remove_instance_variable(:@authority) if defined?(@authority)
-
remove_instance_variable(:@normalized_authority) if defined?(@normalized_authority)
-
remove_composite_values
-
-
# Ensure we haven't created an invalid URI
-
validate()
-
end
-
-
# Returns an array of known ip-based schemes. These schemes typically
-
# use a similar URI form:
-
# <code>//<user>:<password>@<host>:<port>/<url-path></code>
-
1
def self.ip_based_schemes
-
52
return self.port_mapping.keys
-
end
-
-
# Returns a hash of common IP-based schemes and their default port
-
# numbers. Adding new schemes to this hash, as necessary, will allow
-
# for better URI normalization.
-
1
def self.port_mapping
-
84
PORT_MAPPING
-
end
-
-
##
-
# The port component for this URI.
-
# This is the port number actually given in the URI. This does not
-
# infer port numbers from default values.
-
#
-
# @return [Integer] The port component.
-
1
def port
-
176
return defined?(@port) ? @port : nil
-
end
-
-
##
-
# The port component for this URI, normalized.
-
#
-
# @return [Integer] The port component, normalized.
-
1
def normalized_port
-
24
return nil unless self.port
-
24
return @normalized_port if defined?(@normalized_port)
-
@normalized_port ||= begin
-
16
if URI.port_mapping[self.normalized_scheme] == self.port
-
16
nil
-
else
-
self.port
-
end
-
16
end
-
end
-
-
##
-
# Sets the port component for this URI.
-
#
-
# @param [String, Integer, #to_s] new_port The new port component.
-
1
def port=(new_port)
-
52
if new_port != nil && new_port.respond_to?(:to_str)
-
4
new_port = Addressable::URI.unencode_component(new_port.to_str)
-
end
-
-
52
if new_port.respond_to?(:valid_encoding?) && !new_port.valid_encoding?
-
raise InvalidURIError, "Invalid encoding in port"
-
end
-
-
52
if new_port != nil && !(new_port.to_s =~ /^\d+$/)
-
raise InvalidURIError,
-
"Invalid port number: #{new_port.inspect}"
-
end
-
-
52
@port = new_port.to_s.to_i
-
52
@port = nil if @port == 0
-
-
# Reset dependent values
-
52
remove_instance_variable(:@authority) if defined?(@authority)
-
52
remove_instance_variable(:@normalized_port) if defined?(@normalized_port)
-
52
remove_composite_values
-
-
# Ensure we haven't created an invalid URI
-
52
validate()
-
end
-
-
##
-
# The inferred port component for this URI.
-
# This method will normalize to the default port for the URI's scheme if
-
# the port isn't explicitly specified in the URI.
-
#
-
# @return [Integer] The inferred port component.
-
1
def inferred_port
-
8
if self.port.to_i == 0
-
8
self.default_port
-
else
-
self.port.to_i
-
end
-
end
-
-
##
-
# The default port for this URI's scheme.
-
# This method will always returns the default port for the URI's scheme
-
# regardless of the presence of an explicit port in the URI.
-
#
-
# @return [Integer] The default port.
-
1
def default_port
-
8
URI.port_mapping[self.scheme.strip.downcase] if self.scheme
-
end
-
-
##
-
# The combination of components that represent a site.
-
# Combines the scheme, user, password, host, and port components.
-
# Primarily useful for HTTP and HTTPS.
-
#
-
# For example, <code>"http://example.com/path?query"</code> would have a
-
# <code>site</code> value of <code>"http://example.com"</code>.
-
#
-
# @return [String] The components that identify a site.
-
1
def site
-
8
(self.scheme || self.authority) && @site ||= begin
-
8
site_string = ""
-
8
site_string << "#{self.scheme}:" if self.scheme != nil
-
8
site_string << "//#{self.authority}" if self.authority != nil
-
8
site_string
-
8
end
-
end
-
-
##
-
# The normalized combination of components that represent a site.
-
# Combines the scheme, user, password, host, and port components.
-
# Primarily useful for HTTP and HTTPS.
-
#
-
# For example, <code>"http://example.com/path?query"</code> would have a
-
# <code>site</code> value of <code>"http://example.com"</code>.
-
#
-
# @return [String] The normalized components that identify a site.
-
1
def normalized_site
-
8
return nil unless self.site
-
@normalized_site ||= begin
-
8
site_string = ""
-
8
if self.normalized_scheme != nil
-
8
site_string << "#{self.normalized_scheme}:"
-
end
-
8
if self.normalized_authority != nil
-
8
site_string << "//#{self.normalized_authority}"
-
end
-
8
site_string
-
8
end
-
end
-
-
##
-
# Sets the site value for this URI.
-
#
-
# @param [String, #to_str] new_site The new site value.
-
1
def site=(new_site)
-
if new_site
-
if !new_site.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_site.class} into String."
-
end
-
new_site = new_site.to_str
-
# These two regular expressions derived from the primary parsing
-
# expression
-
self.scheme = new_site[/^(?:([^:\/?#]+):)?(?:\/\/(?:[^\/?#]*))?$/, 1]
-
self.authority = new_site[
-
/^(?:(?:[^:\/?#]+):)?(?:\/\/([^\/?#]*))?$/, 1
-
]
-
else
-
self.scheme = nil
-
self.authority = nil
-
end
-
end
-
-
##
-
# The path component for this URI.
-
#
-
# @return [String] The path component.
-
1
def path
-
292
return defined?(@path) ? @path : EMPTY_STR
-
end
-
-
1
NORMPATH = /^(?!\/)[^\/:]*:.*$/
-
##
-
# The path component for this URI, normalized.
-
#
-
# @return [String] The path component, normalized.
-
1
def normalized_path
-
@normalized_path ||= begin
-
16
path = self.path.to_s
-
16
if self.scheme == nil && path =~ NORMPATH
-
# Relative paths with colons in the first segment are ambiguous.
-
path = path.sub(":", "%2F")
-
end
-
# String#split(delimeter, -1) uses the more strict splitting behavior
-
# found by default in Python.
-
16
result = path.strip.split(SLASH, -1).map do |segment|
-
64
Addressable::URI.normalize_component(
-
segment,
-
Addressable::URI::CharacterClasses::PCHAR
-
)
-
end.join(SLASH)
-
-
16
result = URI.normalize_path(result)
-
if result.empty? &&
-
16
["http", "https", "ftp", "tftp"].include?(self.normalized_scheme)
-
result = SLASH
-
end
-
16
result
-
16
end
-
end
-
-
##
-
# Sets the path component for this URI.
-
#
-
# @param [String, #to_str] new_path The new path component.
-
1
def path=(new_path)
-
44
if new_path && !new_path.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_path.class} into String."
-
end
-
44
@path = (new_path || EMPTY_STR).to_str
-
44
if !@path.empty? && @path[0..0] != SLASH && host != nil
-
@path = "/#{@path}"
-
end
-
-
# Reset dependent values
-
44
remove_instance_variable(:@normalized_path) if defined?(@normalized_path)
-
44
remove_composite_values
-
end
-
-
##
-
# The basename, if any, of the file in the path component.
-
#
-
# @return [String] The path's basename.
-
1
def basename
-
# Path cannot be nil
-
return File.basename(self.path).gsub(/;[^\/]*$/, EMPTY_STR)
-
end
-
-
##
-
# The extname, if any, of the file in the path component.
-
# Empty string if there is no extension.
-
#
-
# @return [String] The path's extname.
-
1
def extname
-
return nil unless self.path
-
return File.extname(self.basename)
-
end
-
-
##
-
# The query component for this URI.
-
#
-
# @return [String] The query component.
-
1
def query
-
224
return defined?(@query) ? @query : nil
-
end
-
-
##
-
# The query component for this URI, normalized.
-
#
-
# @return [String] The query component, normalized.
-
1
def normalized_query(*flags)
-
16
return nil unless self.query
-
16
return @normalized_query if defined?(@normalized_query)
-
@normalized_query ||= begin
-
16
modified_query_class = Addressable::URI::CharacterClasses::QUERY.dup
-
# Make sure possible key-value pair delimiters are escaped.
-
16
modified_query_class.sub!("\\&", "").sub!("\\;", "")
-
16
pairs = (self.query || "").split("&", -1)
-
16
pairs.sort! if flags.include?(:sorted)
-
16
component = pairs.map do |pair|
-
68
Addressable::URI.normalize_component(pair, modified_query_class, "+")
-
end.join("&")
-
16
component == "" ? nil : component
-
16
end
-
end
-
-
##
-
# Sets the query component for this URI.
-
#
-
# @param [String, #to_str] new_query The new query component.
-
1
def query=(new_query)
-
60
if new_query && !new_query.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_query.class} into String."
-
end
-
60
@query = new_query ? new_query.to_str : nil
-
-
# Reset dependent values
-
60
remove_instance_variable(:@normalized_query) if defined?(@normalized_query)
-
60
remove_composite_values
-
end
-
-
##
-
# Converts the query component to a Hash value.
-
#
-
# @param [Class] return_type The return type desired. Value must be either
-
# `Hash` or `Array`.
-
#
-
# @return [Hash, Array, nil] The query string parsed as a Hash or Array
-
# or nil if the query string is blank.
-
#
-
# @example
-
# Addressable::URI.parse("?one=1&two=2&three=3").query_values
-
# #=> {"one" => "1", "two" => "2", "three" => "3"}
-
# Addressable::URI.parse("?one=two&one=three").query_values(Array)
-
# #=> [["one", "two"], ["one", "three"]]
-
# Addressable::URI.parse("?one=two&one=three").query_values(Hash)
-
# #=> {"one" => "three"}
-
# Addressable::URI.parse("?").query_values
-
# #=> {}
-
# Addressable::URI.parse("").query_values
-
# #=> nil
-
1
def query_values(return_type=Hash)
-
8
empty_accumulator = Array == return_type ? [] : {}
-
8
if return_type != Hash && return_type != Array
-
raise ArgumentError, "Invalid return type. Must be Hash or Array."
-
end
-
8
return nil if self.query == nil
-
8
split_query = self.query.split("&").map do |pair|
-
34
pair.split("=", 2) if pair && !pair.empty?
-
end.compact
-
8
return split_query.inject(empty_accumulator.dup) do |accu, pair|
-
# I'd rather use key/value identifiers instead of array lookups,
-
# but in this case I really want to maintain the exact pair structure,
-
# so it's best to make all changes in-place.
-
34
pair[0] = URI.unencode_component(pair[0])
-
34
if pair[1].respond_to?(:to_str)
-
# I loathe the fact that I have to do this. Stupid HTML 4.01.
-
# Treating '+' as a space was just an unbelievably bad idea.
-
# There was nothing wrong with '%20'!
-
# If it ain't broke, don't fix it!
-
34
pair[1] = URI.unencode_component(pair[1].to_str.gsub(/\+/, " "))
-
end
-
34
if return_type == Hash
-
34
accu[pair[0]] = pair[1]
-
else
-
accu << pair
-
end
-
34
accu
-
end
-
end
-
-
##
-
# Sets the query component for this URI from a Hash object.
-
# An empty Hash or Array will result in an empty query string.
-
#
-
# @param [Hash, #to_hash, Array] new_query_values The new query values.
-
#
-
# @example
-
# uri.query_values = {:a => "a", :b => ["c", "d", "e"]}
-
# uri.query
-
# # => "a=a&b=c&b=d&b=e"
-
# uri.query_values = [['a', 'a'], ['b', 'c'], ['b', 'd'], ['b', 'e']]
-
# uri.query
-
# # => "a=a&b=c&b=d&b=e"
-
# uri.query_values = [['a', 'a'], ['b', ['c', 'd', 'e']]]
-
# uri.query
-
# # => "a=a&b=c&b=d&b=e"
-
# uri.query_values = [['flag'], ['key', 'value']]
-
# uri.query
-
# # => "flag&key=value"
-
1
def query_values=(new_query_values)
-
if new_query_values == nil
-
self.query = nil
-
return nil
-
end
-
-
if !new_query_values.is_a?(Array)
-
if !new_query_values.respond_to?(:to_hash)
-
raise TypeError,
-
"Can't convert #{new_query_values.class} into Hash."
-
end
-
new_query_values = new_query_values.to_hash
-
new_query_values = new_query_values.map do |key, value|
-
key = key.to_s if key.kind_of?(Symbol)
-
[key, value]
-
end
-
# Useful default for OAuth and caching.
-
# Only to be used for non-Array inputs. Arrays should preserve order.
-
new_query_values.sort!
-
end
-
-
# new_query_values have form [['key1', 'value1'], ['key2', 'value2']]
-
buffer = ""
-
new_query_values.each do |key, value|
-
encoded_key = URI.encode_component(
-
key, CharacterClasses::UNRESERVED
-
)
-
if value == nil
-
buffer << "#{encoded_key}&"
-
elsif value.kind_of?(Array)
-
value.each do |sub_value|
-
encoded_value = URI.encode_component(
-
sub_value, CharacterClasses::UNRESERVED
-
)
-
buffer << "#{encoded_key}=#{encoded_value}&"
-
end
-
else
-
encoded_value = URI.encode_component(
-
value, CharacterClasses::UNRESERVED
-
)
-
buffer << "#{encoded_key}=#{encoded_value}&"
-
end
-
end
-
self.query = buffer.chop
-
end
-
-
##
-
# The HTTP request URI for this URI. This is the path and the
-
# query string.
-
#
-
# @return [String] The request URI required for an HTTP request.
-
1
def request_uri
-
return nil if self.absolute? && self.scheme !~ /^https?$/
-
return (
-
(!self.path.empty? ? self.path : SLASH) +
-
(self.query ? "?#{self.query}" : EMPTY_STR)
-
)
-
end
-
-
##
-
# Sets the HTTP request URI for this URI.
-
#
-
# @param [String, #to_str] new_request_uri The new HTTP request URI.
-
1
def request_uri=(new_request_uri)
-
if !new_request_uri.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_request_uri.class} into String."
-
end
-
if self.absolute? && self.scheme !~ /^https?$/
-
raise InvalidURIError,
-
"Cannot set an HTTP request URI for a non-HTTP URI."
-
end
-
new_request_uri = new_request_uri.to_str
-
path_component = new_request_uri[/^([^\?]*)\?(?:.*)$/, 1]
-
query_component = new_request_uri[/^(?:[^\?]*)\?(.*)$/, 1]
-
path_component = path_component.to_s
-
path_component = (!path_component.empty? ? path_component : SLASH)
-
self.path = path_component
-
self.query = query_component
-
-
# Reset dependent values
-
remove_composite_values
-
end
-
-
##
-
# The fragment component for this URI.
-
#
-
# @return [String] The fragment component.
-
1
def fragment
-
92
return defined?(@fragment) ? @fragment : nil
-
end
-
-
##
-
# The fragment component for this URI, normalized.
-
#
-
# @return [String] The fragment component, normalized.
-
1
def normalized_fragment
-
16
return nil unless self.fragment
-
return @normalized_fragment if defined?(@normalized_fragment)
-
@normalized_fragment ||= begin
-
component = Addressable::URI.normalize_component(
-
self.fragment,
-
Addressable::URI::CharacterClasses::FRAGMENT
-
)
-
component == "" ? nil : component
-
end
-
end
-
-
##
-
# Sets the fragment component for this URI.
-
#
-
# @param [String, #to_str] new_fragment The new fragment component.
-
1
def fragment=(new_fragment)
-
if new_fragment && !new_fragment.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_fragment.class} into String."
-
end
-
@fragment = new_fragment ? new_fragment.to_str : nil
-
-
# Reset dependent values
-
remove_instance_variable(:@normalized_fragment) if defined?(@normalized_fragment)
-
remove_composite_values
-
-
# Ensure we haven't created an invalid URI
-
validate()
-
end
-
-
##
-
# Determines if the scheme indicates an IP-based protocol.
-
#
-
# @return [TrueClass, FalseClass]
-
# <code>true</code> if the scheme indicates an IP-based protocol.
-
# <code>false</code> otherwise.
-
1
def ip_based?
-
52
if self.scheme
-
return URI.ip_based_schemes.include?(
-
52
self.scheme.strip.downcase)
-
end
-
return false
-
end
-
-
##
-
# Determines if the URI is relative.
-
#
-
# @return [TrueClass, FalseClass]
-
# <code>true</code> if the URI is relative. <code>false</code>
-
# otherwise.
-
1
def relative?
-
return self.scheme.nil?
-
end
-
-
##
-
# Determines if the URI is absolute.
-
#
-
# @return [TrueClass, FalseClass]
-
# <code>true</code> if the URI is absolute. <code>false</code>
-
# otherwise.
-
1
def absolute?
-
return !relative?
-
end
-
-
##
-
# Joins two URIs together.
-
#
-
# @param [String, Addressable::URI, #to_str] The URI to join with.
-
#
-
# @return [Addressable::URI] The joined URI.
-
1
def join(uri)
-
if !uri.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{uri.class} into String."
-
end
-
if !uri.kind_of?(URI)
-
# Otherwise, convert to a String, then parse.
-
uri = URI.parse(uri.to_str)
-
end
-
if uri.to_s.empty?
-
return self.dup
-
end
-
-
joined_scheme = nil
-
joined_user = nil
-
joined_password = nil
-
joined_host = nil
-
joined_port = nil
-
joined_path = nil
-
joined_query = nil
-
joined_fragment = nil
-
-
# Section 5.2.2 of RFC 3986
-
if uri.scheme != nil
-
joined_scheme = uri.scheme
-
joined_user = uri.user
-
joined_password = uri.password
-
joined_host = uri.host
-
joined_port = uri.port
-
joined_path = URI.normalize_path(uri.path)
-
joined_query = uri.query
-
else
-
if uri.authority != nil
-
joined_user = uri.user
-
joined_password = uri.password
-
joined_host = uri.host
-
joined_port = uri.port
-
joined_path = URI.normalize_path(uri.path)
-
joined_query = uri.query
-
else
-
if uri.path == nil || uri.path.empty?
-
joined_path = self.path
-
if uri.query != nil
-
joined_query = uri.query
-
else
-
joined_query = self.query
-
end
-
else
-
if uri.path[0..0] == SLASH
-
joined_path = URI.normalize_path(uri.path)
-
else
-
base_path = self.path.dup
-
base_path = EMPTY_STR if base_path == nil
-
base_path = URI.normalize_path(base_path)
-
-
# Section 5.2.3 of RFC 3986
-
#
-
# Removes the right-most path segment from the base path.
-
if base_path =~ /\//
-
base_path.gsub!(/\/[^\/]+$/, SLASH)
-
else
-
base_path = EMPTY_STR
-
end
-
-
# If the base path is empty and an authority segment has been
-
# defined, use a base path of SLASH
-
if base_path.empty? && self.authority != nil
-
base_path = SLASH
-
end
-
-
joined_path = URI.normalize_path(base_path + uri.path)
-
end
-
joined_query = uri.query
-
end
-
joined_user = self.user
-
joined_password = self.password
-
joined_host = self.host
-
joined_port = self.port
-
end
-
joined_scheme = self.scheme
-
end
-
joined_fragment = uri.fragment
-
-
return self.class.new(
-
:scheme => joined_scheme,
-
:user => joined_user,
-
:password => joined_password,
-
:host => joined_host,
-
:port => joined_port,
-
:path => joined_path,
-
:query => joined_query,
-
:fragment => joined_fragment
-
)
-
end
-
1
alias_method :+, :join
-
-
##
-
# Destructive form of <code>join</code>.
-
#
-
# @param [String, Addressable::URI, #to_str] The URI to join with.
-
#
-
# @return [Addressable::URI] The joined URI.
-
#
-
# @see Addressable::URI#join
-
1
def join!(uri)
-
replace_self(self.join(uri))
-
end
-
-
##
-
# Merges a URI with a <code>Hash</code> of components.
-
# This method has different behavior from <code>join</code>. Any
-
# components present in the <code>hash</code> parameter will override the
-
# original components. The path component is not treated specially.
-
#
-
# @param [Hash, Addressable::URI, #to_hash] The components to merge with.
-
#
-
# @return [Addressable::URI] The merged URI.
-
#
-
# @see Hash#merge
-
1
def merge(hash)
-
if !hash.respond_to?(:to_hash)
-
raise TypeError, "Can't convert #{hash.class} into Hash."
-
end
-
hash = hash.to_hash
-
-
if hash.has_key?(:authority)
-
if (hash.keys & [:userinfo, :user, :password, :host, :port]).any?
-
raise ArgumentError,
-
"Cannot specify both an authority and any of the components " +
-
"within the authority."
-
end
-
end
-
if hash.has_key?(:userinfo)
-
if (hash.keys & [:user, :password]).any?
-
raise ArgumentError,
-
"Cannot specify both a userinfo and either the user or password."
-
end
-
end
-
-
uri = self.class.new
-
uri.defer_validation do
-
# Bunch of crazy logic required because of the composite components
-
# like userinfo and authority.
-
uri.scheme =
-
hash.has_key?(:scheme) ? hash[:scheme] : self.scheme
-
if hash.has_key?(:authority)
-
uri.authority =
-
hash.has_key?(:authority) ? hash[:authority] : self.authority
-
end
-
if hash.has_key?(:userinfo)
-
uri.userinfo =
-
hash.has_key?(:userinfo) ? hash[:userinfo] : self.userinfo
-
end
-
if !hash.has_key?(:userinfo) && !hash.has_key?(:authority)
-
uri.user =
-
hash.has_key?(:user) ? hash[:user] : self.user
-
uri.password =
-
hash.has_key?(:password) ? hash[:password] : self.password
-
end
-
if !hash.has_key?(:authority)
-
uri.host =
-
hash.has_key?(:host) ? hash[:host] : self.host
-
uri.port =
-
hash.has_key?(:port) ? hash[:port] : self.port
-
end
-
uri.path =
-
hash.has_key?(:path) ? hash[:path] : self.path
-
uri.query =
-
hash.has_key?(:query) ? hash[:query] : self.query
-
uri.fragment =
-
hash.has_key?(:fragment) ? hash[:fragment] : self.fragment
-
end
-
-
return uri
-
end
-
-
##
-
# Destructive form of <code>merge</code>.
-
#
-
# @param [Hash, Addressable::URI, #to_hash] The components to merge with.
-
#
-
# @return [Addressable::URI] The merged URI.
-
#
-
# @see Addressable::URI#merge
-
1
def merge!(uri)
-
replace_self(self.merge(uri))
-
end
-
-
##
-
# Returns the shortest normalized relative form of this URI that uses the
-
# supplied URI as a base for resolution. Returns an absolute URI if
-
# necessary. This is effectively the opposite of <code>route_to</code>.
-
#
-
# @param [String, Addressable::URI, #to_str] uri The URI to route from.
-
#
-
# @return [Addressable::URI]
-
# The normalized relative URI that is equivalent to the original URI.
-
1
def route_from(uri)
-
uri = URI.parse(uri).normalize
-
normalized_self = self.normalize
-
if normalized_self.relative?
-
raise ArgumentError, "Expected absolute URI, got: #{self.to_s}"
-
end
-
if uri.relative?
-
raise ArgumentError, "Expected absolute URI, got: #{uri.to_s}"
-
end
-
if normalized_self == uri
-
return Addressable::URI.parse("##{normalized_self.fragment}")
-
end
-
components = normalized_self.to_hash
-
if normalized_self.scheme == uri.scheme
-
components[:scheme] = nil
-
if normalized_self.authority == uri.authority
-
components[:user] = nil
-
components[:password] = nil
-
components[:host] = nil
-
components[:port] = nil
-
if normalized_self.path == uri.path
-
components[:path] = nil
-
if normalized_self.query == uri.query
-
components[:query] = nil
-
end
-
else
-
if uri.path != SLASH and components[:path]
-
self_splitted_path = split_path(components[:path])
-
uri_splitted_path = split_path(uri.path)
-
self_dir = self_splitted_path.shift
-
uri_dir = uri_splitted_path.shift
-
while !self_splitted_path.empty? && !uri_splitted_path.empty? and self_dir == uri_dir
-
self_dir = self_splitted_path.shift
-
uri_dir = uri_splitted_path.shift
-
end
-
components[:path] = (uri_splitted_path.fill('..') + [self_dir] + self_splitted_path).join(SLASH)
-
end
-
end
-
end
-
end
-
# Avoid network-path references.
-
if components[:host] != nil
-
components[:scheme] = normalized_self.scheme
-
end
-
return Addressable::URI.new(
-
:scheme => components[:scheme],
-
:user => components[:user],
-
:password => components[:password],
-
:host => components[:host],
-
:port => components[:port],
-
:path => components[:path],
-
:query => components[:query],
-
:fragment => components[:fragment]
-
)
-
end
-
-
##
-
# Returns the shortest normalized relative form of the supplied URI that
-
# uses this URI as a base for resolution. Returns an absolute URI if
-
# necessary. This is effectively the opposite of <code>route_from</code>.
-
#
-
# @param [String, Addressable::URI, #to_str] uri The URI to route to.
-
#
-
# @return [Addressable::URI]
-
# The normalized relative URI that is equivalent to the supplied URI.
-
1
def route_to(uri)
-
return URI.parse(uri).route_from(self)
-
end
-
-
##
-
# Returns a normalized URI object.
-
#
-
# NOTE: This method does not attempt to fully conform to specifications.
-
# It exists largely to correct other people's failures to read the
-
# specifications, and also to deal with caching issues since several
-
# different URIs may represent the same resource and should not be
-
# cached multiple times.
-
#
-
# @return [Addressable::URI] The normalized URI.
-
1
def normalize
-
# This is a special exception for the frequently misused feed
-
# URI scheme.
-
8
if normalized_scheme == "feed"
-
if self.to_s =~ /^feed:\/*http:\/*/
-
return URI.parse(
-
self.to_s[/^feed:\/*(http:\/*.*)/, 1]
-
).normalize
-
end
-
end
-
-
8
return self.class.new(
-
:scheme => normalized_scheme,
-
:authority => normalized_authority,
-
:path => normalized_path,
-
:query => normalized_query,
-
:fragment => normalized_fragment
-
)
-
end
-
-
##
-
# Destructively normalizes this URI object.
-
#
-
# @return [Addressable::URI] The normalized URI.
-
#
-
# @see Addressable::URI#normalize
-
1
def normalize!
-
replace_self(self.normalize)
-
end
-
-
##
-
# Creates a URI suitable for display to users. If semantic attacks are
-
# likely, the application should try to detect these and warn the user.
-
# See <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>,
-
# section 7.6 for more information.
-
#
-
# @return [Addressable::URI] A URI suitable for display purposes.
-
1
def display_uri
-
display_uri = self.normalize
-
display_uri.host = ::Addressable::IDNA.to_unicode(display_uri.host)
-
return display_uri
-
end
-
-
##
-
# Returns <code>true</code> if the URI objects are equal. This method
-
# normalizes both URIs before doing the comparison, and allows comparison
-
# against <code>Strings</code>.
-
#
-
# @param [Object] uri The URI to compare.
-
#
-
# @return [TrueClass, FalseClass]
-
# <code>true</code> if the URIs are equivalent, <code>false</code>
-
# otherwise.
-
1
def ===(uri)
-
if uri.respond_to?(:normalize)
-
uri_string = uri.normalize.to_s
-
else
-
begin
-
uri_string = ::Addressable::URI.parse(uri).normalize.to_s
-
rescue InvalidURIError, TypeError
-
return false
-
end
-
end
-
return self.normalize.to_s == uri_string
-
end
-
-
##
-
# Returns <code>true</code> if the URI objects are equal. This method
-
# normalizes both URIs before doing the comparison.
-
#
-
# @param [Object] uri The URI to compare.
-
#
-
# @return [TrueClass, FalseClass]
-
# <code>true</code> if the URIs are equivalent, <code>false</code>
-
# otherwise.
-
1
def ==(uri)
-
return false unless uri.kind_of?(URI)
-
return self.normalize.to_s == uri.normalize.to_s
-
end
-
-
##
-
# Returns <code>true</code> if the URI objects are equal. This method
-
# does NOT normalize either URI before doing the comparison.
-
#
-
# @param [Object] uri The URI to compare.
-
#
-
# @return [TrueClass, FalseClass]
-
# <code>true</code> if the URIs are equivalent, <code>false</code>
-
# otherwise.
-
1
def eql?(uri)
-
4
return false unless uri.kind_of?(URI)
-
4
return self.to_s == uri.to_s
-
end
-
-
##
-
# A hash value that will make a URI equivalent to its normalized
-
# form.
-
#
-
# @return [Integer] A hash of the URI.
-
1
def hash
-
28
@hash ||= self.to_s.hash * -1
-
end
-
-
##
-
# Clones the URI object.
-
#
-
# @return [Addressable::URI] The cloned URI.
-
1
def dup
-
32
duplicated_uri = self.class.new(
-
:scheme => self.scheme ? self.scheme.dup : nil,
-
:user => self.user ? self.user.dup : nil,
-
:password => self.password ? self.password.dup : nil,
-
:host => self.host ? self.host.dup : nil,
-
:port => self.port,
-
:path => self.path ? self.path.dup : nil,
-
:query => self.query ? self.query.dup : nil,
-
:fragment => self.fragment ? self.fragment.dup : nil
-
)
-
32
return duplicated_uri
-
end
-
-
##
-
# Omits components from a URI.
-
#
-
# @param [Symbol] *components The components to be omitted.
-
#
-
# @return [Addressable::URI] The URI with components omitted.
-
#
-
# @example
-
# uri = Addressable::URI.parse("http://example.com/path?query")
-
# #=> #<Addressable::URI:0xcc5e7a URI:http://example.com/path?query>
-
# uri.omit(:scheme, :authority)
-
# #=> #<Addressable::URI:0xcc4d86 URI:/path?query>
-
1
def omit(*components)
-
invalid_components = components - [
-
:scheme, :user, :password, :userinfo, :host, :port, :authority,
-
:path, :query, :fragment
-
]
-
unless invalid_components.empty?
-
raise ArgumentError,
-
"Invalid component names: #{invalid_components.inspect}."
-
end
-
duplicated_uri = self.dup
-
duplicated_uri.defer_validation do
-
components.each do |component|
-
duplicated_uri.send((component.to_s + "=").to_sym, nil)
-
end
-
duplicated_uri.user = duplicated_uri.normalized_user
-
end
-
duplicated_uri
-
end
-
-
##
-
# Destructive form of omit.
-
#
-
# @param [Symbol] *components The components to be omitted.
-
#
-
# @return [Addressable::URI] The URI with components omitted.
-
#
-
# @see Addressable::URI#omit
-
1
def omit!(*components)
-
replace_self(self.omit(*components))
-
end
-
-
##
-
# Determines if the URI is an empty string.
-
#
-
# @return [TrueClass, FalseClass]
-
# Returns <code>true</code> if empty, <code>false</code> otherwise.
-
1
def empty?
-
return self.to_s.empty?
-
end
-
-
##
-
# Converts the URI to a <code>String</code>.
-
#
-
# @return [String] The URI's <code>String</code> representation.
-
1
def to_s
-
99
if self.scheme == nil && self.path != nil && !self.path.empty? &&
-
self.path =~ NORMPATH
-
raise InvalidURIError,
-
"Cannot assemble URI string with ambiguous path: '#{self.path}'"
-
end
-
@uri_string ||= begin
-
44
uri_string = ""
-
44
uri_string << "#{self.scheme}:" if self.scheme != nil
-
44
uri_string << "//#{self.authority}" if self.authority != nil
-
44
uri_string << self.path.to_s
-
44
uri_string << "?#{self.query}" if self.query != nil
-
44
uri_string << "##{self.fragment}" if self.fragment != nil
-
44
if uri_string.respond_to?(:force_encoding)
-
44
uri_string.force_encoding(Encoding::UTF_8)
-
end
-
44
uri_string
-
99
end
-
end
-
-
##
-
# URI's are glorified <code>Strings</code>. Allow implicit conversion.
-
1
alias_method :to_str, :to_s
-
-
##
-
# Returns a Hash of the URI components.
-
#
-
# @return [Hash] The URI as a <code>Hash</code> of components.
-
1
def to_hash
-
return {
-
:scheme => self.scheme,
-
:user => self.user,
-
:password => self.password,
-
:host => self.host,
-
:port => self.port,
-
:path => self.path,
-
:query => self.query,
-
:fragment => self.fragment
-
}
-
end
-
-
##
-
# Returns a <code>String</code> representation of the URI object's state.
-
#
-
# @return [String] The URI object's state, as a <code>String</code>.
-
1
def inspect
-
sprintf("#<%s:%#0x URI:%s>", URI.to_s, self.object_id, self.to_s)
-
end
-
-
##
-
# This method allows you to make several changes to a URI simultaneously,
-
# which separately would cause validation errors, but in conjunction,
-
# are valid. The URI will be revalidated as soon as the entire block has
-
# been executed.
-
#
-
# @param [Proc] block
-
# A set of operations to perform on a given URI.
-
1
def defer_validation(&block)
-
44
raise LocalJumpError, "No block given." unless block
-
44
@validation_deferred = true
-
44
block.call()
-
44
@validation_deferred = false
-
44
validate
-
return nil
-
end
-
-
1
protected
-
1
SELF_REF = '.'
-
1
PARENT = '..'
-
-
1
RULE_2A = /\/\.\/|\/\.$/
-
1
RULE_2B_2C = /\/([^\/]*)\/\.\.\/|\/([^\/]*)\/\.\.$/
-
1
RULE_2D = /^\.\.?\/?/
-
1
RULE_PREFIXED_PARENT = /^\/\.\.?\/|^(\/\.\.?)+\/?$/
-
-
##
-
# Resolves paths to their simplest form.
-
#
-
# @param [String] path The path to normalize.
-
#
-
# @return [String] The normalized path.
-
1
def self.normalize_path(path)
-
# Section 5.2.4 of RFC 3986
-
-
16
return nil if path.nil?
-
16
normalized_path = path.dup
-
begin
-
16
mod = nil
-
16
mod ||= normalized_path.gsub!(RULE_2A, SLASH)
-
-
16
pair = normalized_path.match(RULE_2B_2C)
-
16
parent, current = pair[1], pair[2] if pair
-
if pair && ((parent != SELF_REF && parent != PARENT) ||
-
16
(current != SELF_REF && current != PARENT))
-
mod ||= normalized_path.gsub!(
-
Regexp.new(
-
"/#{Regexp.escape(parent.to_s)}/\\.\\./|" +
-
"(/#{Regexp.escape(current.to_s)}/\\.\\.$)"
-
), SLASH
-
)
-
end
-
-
16
mod ||= normalized_path.gsub!(RULE_2D, EMPTY_STR)
-
# Non-standard, removes prefixed dotted segments from path.
-
16
mod ||= normalized_path.gsub!(RULE_PREFIXED_PARENT, SLASH)
-
16
end until mod.nil?
-
-
16
return normalized_path
-
end
-
-
##
-
# Ensures that the URI is valid.
-
1
def validate
-
208
return if !!@validation_deferred
-
52
if self.scheme != nil && self.ip_based? &&
-
(self.host == nil || self.host.empty?) &&
-
(self.path == nil || self.path.empty?)
-
raise InvalidURIError,
-
"Absolute URI missing hierarchical segment: '#{self.to_s}'"
-
end
-
52
if self.host == nil
-
if self.port != nil ||
-
self.user != nil ||
-
self.password != nil
-
raise InvalidURIError, "Hostname not supplied: '#{self.to_s}'"
-
end
-
end
-
52
if self.path != nil && !self.path.empty? && self.path[0..0] != SLASH &&
-
self.authority != nil
-
raise InvalidURIError,
-
"Cannot have a relative path with an authority set: '#{self.to_s}'"
-
end
-
return nil
-
end
-
-
##
-
# Replaces the internal state of self with the specified URI's state.
-
# Used in destructive operations to avoid massive code repetition.
-
#
-
# @param [Addressable::URI] uri The URI to replace <code>self</code> with.
-
#
-
# @return [Addressable::URI] <code>self</code>.
-
1
def replace_self(uri)
-
# Reset dependent values
-
instance_variables.each do |var|
-
remove_instance_variable(var) if instance_variable_defined?(var)
-
end
-
-
@scheme = uri.scheme
-
@user = uri.user
-
@password = uri.password
-
@host = uri.host
-
@port = uri.port
-
@path = uri.path
-
@query = uri.query
-
@fragment = uri.fragment
-
return self
-
end
-
-
##
-
# Splits path string with "/" (slash).
-
# It is considered that there is empty string after last slash when
-
# path ends with slash.
-
#
-
# @param [String] path The path to split.
-
#
-
# @return [Array<String>] An array of parts of path.
-
1
def split_path(path)
-
splitted = path.split(SLASH)
-
splitted << EMPTY_STR if path.end_with? SLASH
-
splitted
-
end
-
-
##
-
# Resets composite values for the entire URI
-
#
-
# @api private
-
1
def remove_composite_values
-
268
remove_instance_variable(:@uri_string) if defined?(@uri_string)
-
268
remove_instance_variable(:@hash) if defined?(@hash)
-
end
-
end
-
end
-
# encoding:utf-8
-
#--
-
# Copyright (C) 2006-2015 Bob Aman
-
#
-
# Licensed under the Apache License, Version 2.0 (the "License");
-
# you may not use this file except in compliance with the License.
-
# You may obtain a copy of the License at
-
#
-
# http://www.apache.org/licenses/LICENSE-2.0
-
#
-
# Unless required by applicable law or agreed to in writing, software
-
# distributed under the License is distributed on an "AS IS" BASIS,
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-
# See the License for the specific language governing permissions and
-
# limitations under the License.
-
#++
-
-
-
# Used to prevent the class/module from being loaded more than once
-
1
if !defined?(Addressable::VERSION)
-
1
module Addressable
-
1
module VERSION
-
1
MAJOR = 2
-
1
MINOR = 4
-
1
TINY = 0
-
-
1
STRING = [MAJOR, MINOR, TINY].join('.')
-
end
-
end
-
end
-
1
require 'rexml/parsers/streamparser'
-
1
require 'rexml/parsers/baseparser'
-
1
require 'rexml/light/node'
-
1
require 'rexml/text'
-
1
require "rexml/document"
-
1
require 'date'
-
1
require 'time'
-
1
require 'yaml'
-
1
require 'bigdecimal'
-
-
# The Reason behind redefining the String Class for this specific plugin is to
-
# avoid the dynamic insertion of stuff on it (see version previous to this commit).
-
# Doing that disables the possibility of efectuating a dump on the structure. This way it goes.
-
1
class REXMLUtiliyNodeString < String
-
1
attr_accessor :attributes
-
end
-
-
# This is a slighly modified version of the XMLUtilityNode from
-
# http://merb.devjavu.com/projects/merb/ticket/95 (has.sox@gmail.com)
-
# It's mainly just adding vowels, as I ht cd wth n vwls :)
-
# This represents the hard part of the work, all I did was change the
-
# underlying parser.
-
1
class REXMLUtilityNode #:nodoc:
-
1
attr_accessor :name, :attributes, :children, :type
-
-
1
def self.typecasts
-
11
@@typecasts
-
end
-
-
1
def self.typecasts=(obj)
-
1
@@typecasts = obj
-
end
-
-
1
def self.available_typecasts
-
@@available_typecasts
-
end
-
-
1
def self.available_typecasts=(obj)
-
1
@@available_typecasts = obj
-
end
-
-
1
self.typecasts = {}
-
1
self.typecasts["integer"] = lambda{|v| v.nil? ? nil : v.to_i}
-
1
self.typecasts["boolean"] = lambda{|v| v.nil? ? nil : (v.strip != "false")}
-
1
self.typecasts["datetime"] = lambda{|v| v.nil? ? nil : Time.parse(v).utc}
-
1
self.typecasts["date"] = lambda{|v| v.nil? ? nil : Date.parse(v)}
-
1
self.typecasts["dateTime"] = lambda{|v| v.nil? ? nil : Time.parse(v).utc}
-
1
self.typecasts["decimal"] = lambda{|v| v.nil? ? nil : BigDecimal(v.to_s)}
-
1
self.typecasts["double"] = lambda{|v| v.nil? ? nil : v.to_f}
-
1
self.typecasts["float"] = lambda{|v| v.nil? ? nil : v.to_f}
-
1
self.typecasts["string"] = lambda{|v| v.to_s}
-
1
self.typecasts["base64Binary"] = lambda{|v| v.unpack('m').first }
-
-
1
self.available_typecasts = self.typecasts.keys
-
-
1
def initialize(name, normalized_attributes = {})
-
-
# unnormalize attribute values
-
attributes = Hash[* normalized_attributes.map { |key, value|
-
[ key, unnormalize_xml_entities(value) ]
-
}.flatten]
-
-
@name = name.tr("-", "_")
-
# leave the type alone if we don't know what it is
-
@type = self.class.available_typecasts.include?(attributes["type"]) ? attributes.delete("type") : attributes["type"]
-
-
@nil_element = attributes.delete("nil") == "true"
-
@attributes = undasherize_keys(attributes)
-
@children = []
-
@text = false
-
end
-
-
1
def add_node(node)
-
@text = true if node.is_a? String
-
@children << node
-
end
-
-
1
def to_hash
-
# ACG: Added a check here to prevent an exception a type == "file" tag has nodes within it
-
if @type == "file" and (@children.first.nil? or @children.first.is_a?(String))
-
f = StringIO.new((@children.first || '').unpack('m').first)
-
class << f
-
attr_accessor :original_filename, :content_type
-
end
-
f.original_filename = attributes['name'] || 'untitled'
-
f.content_type = attributes['content_type'] || 'application/octet-stream'
-
return {name => f}
-
end
-
-
if @text
-
t = typecast_value( unnormalize_xml_entities( inner_html ) )
-
if t.is_a?(String)
-
t = REXMLUtiliyNodeString.new(t)
-
t.attributes = attributes
-
end
-
return { name => t }
-
else
-
#change repeating groups into an array
-
groups = @children.inject({}) { |s,e| (s[e.name] ||= []) << e; s }
-
-
out = nil
-
if @type == "array"
-
out = []
-
groups.each do |k, v|
-
if v.size == 1
-
out << v.first.to_hash.entries.first.last
-
else
-
out << v.map{|e| e.to_hash[k]}
-
end
-
end
-
out = out.flatten
-
-
else # If Hash
-
out = {}
-
groups.each do |k,v|
-
if v.size == 1
-
out.merge!(v.first)
-
else
-
out.merge!( k => v.map{|e| e.to_hash[k]})
-
end
-
end
-
out.merge! attributes unless attributes.empty?
-
out = out.empty? ? nil : out
-
end
-
-
if @type && out.nil?
-
{ name => typecast_value(out) }
-
else
-
{ name => out }
-
end
-
end
-
end
-
-
# Typecasts a value based upon its type. For instance, if
-
# +node+ has #type == "integer",
-
# {{[node.typecast_value("12") #=> 12]}}
-
#
-
# @param value<String> The value that is being typecast.
-
#
-
# @details [:type options]
-
# "integer"::
-
# converts +value+ to an integer with #to_i
-
# "boolean"::
-
# checks whether +value+, after removing spaces, is the literal
-
# "true"
-
# "datetime"::
-
# Parses +value+ using Time.parse, and returns a UTC Time
-
# "date"::
-
# Parses +value+ using Date.parse
-
#
-
# @return <Integer, TrueClass, FalseClass, Time, Date, Object>
-
# The result of typecasting +value+.
-
#
-
# @note
-
# If +self+ does not have a "type" key, or if it's not one of the
-
# options specified above, the raw +value+ will be returned.
-
1
def typecast_value(value)
-
return value unless @type
-
proc = self.class.typecasts[@type]
-
proc.nil? ? value : proc.call(value)
-
end
-
-
# Take keys of the form foo-bar and convert them to foo_bar
-
1
def undasherize_keys(params)
-
params.keys.each do |key, value|
-
params[key.tr("-", "_")] = params.delete(key)
-
end
-
params
-
end
-
-
# Get the inner_html of the REXML node.
-
1
def inner_html
-
@children.join
-
end
-
-
# Converts the node into a readable HTML node.
-
#
-
# @return <String> The HTML node in text form.
-
1
def to_html
-
attributes.merge!(:type => @type ) if @type
-
"<#{name}#{Crack::Util.to_xml_attributes(attributes)}>#{@nil_element ? '' : inner_html}</#{name}>"
-
end
-
-
# @alias #to_html #to_s
-
1
def to_s
-
to_html
-
end
-
-
1
private
-
-
1
def unnormalize_xml_entities value
-
REXML::Text.unnormalize(value)
-
end
-
end
-
-
1
module Crack
-
1
class REXMLParser
-
1
def self.parse(xml)
-
stack = []
-
parser = REXML::Parsers::BaseParser.new(xml)
-
-
while true
-
event = parser.pull
-
case event[0]
-
when :end_document
-
break
-
when :end_doctype, :start_doctype
-
# do nothing
-
when :start_element
-
stack.push REXMLUtilityNode.new(event[1], event[2])
-
when :end_element
-
if stack.size > 1
-
temp = stack.pop
-
stack.last.add_node(temp)
-
end
-
when :text, :cdata
-
stack.last.add_node(event[1]) unless event[1].strip.length == 0 || stack.empty?
-
end
-
end
-
-
stack.length > 0 ? stack.pop.to_hash : {}
-
end
-
end
-
-
1
class XML
-
1
def self.parser
-
@@parser ||= REXMLParser
-
end
-
-
1
def self.parser=(parser)
-
@@parser = parser
-
end
-
-
1
def self.parse(xml)
-
parser.parse(xml)
-
end
-
end
-
end
-
1
require 'hashdiff/util'
-
1
require 'hashdiff/lcs'
-
1
require 'hashdiff/diff'
-
1
require 'hashdiff/patch'
-
1
require 'hashdiff/version'
-
1
module HashDiff
-
-
# Best diff two objects, which tries to generate the smallest change set using different similarity values.
-
#
-
# HashDiff.best_diff is useful in case of comparing two objects which include similar hashes in arrays.
-
#
-
# @param [Array, Hash] obj1
-
# @param [Array, Hash] obj2
-
# @param [Hash] options the options to use when comparing
-
# * :strict (Boolean) [true] whether numeric values will be compared on type as well as value. Set to false to allow comparing Fixnum, Float, BigDecimal to each other
-
# * :delimiter (String) ['.'] the delimiter used when returning nested key references
-
# * :numeric_tolerance (Numeric) [0] should be a positive numeric value. Value by which numeric differences must be greater than. By default, numeric values are compared exactly; with the :tolerance option, the difference between numeric values must be greater than the given value.
-
# * :strip (Boolean) [false] whether or not to call #strip on strings before comparing
-
#
-
# @yield [path, value1, value2] Optional block is used to compare each value, instead of default #==. If the block returns value other than true of false, then other specified comparison options will be used to do the comparison.
-
#
-
# @return [Array] an array of changes.
-
# e.g. [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']]
-
#
-
# @example
-
# a = {'x' => [{'a' => 1, 'c' => 3, 'e' => 5}, {'y' => 3}]}
-
# b = {'x' => [{'a' => 1, 'b' => 2, 'e' => 5}] }
-
# diff = HashDiff.best_diff(a, b)
-
# diff.should == [['-', 'x[0].c', 3], ['+', 'x[0].b', 2], ['-', 'x[1].y', 3], ['-', 'x[1]', {}]]
-
#
-
# @since 0.0.1
-
1
def self.best_diff(obj1, obj2, options = {}, &block)
-
options[:comparison] = block if block_given?
-
-
opts = { :similarity => 0.3 }.merge!(options)
-
diffs_1 = diff(obj1, obj2, opts)
-
count_1 = count_diff diffs_1
-
-
opts = { :similarity => 0.5 }.merge!(options)
-
diffs_2 = diff(obj1, obj2, opts)
-
count_2 = count_diff diffs_2
-
-
opts = { :similarity => 0.8 }.merge!(options)
-
diffs_3 = diff(obj1, obj2, opts)
-
count_3 = count_diff diffs_3
-
-
count, diffs = count_1 < count_2 ? [count_1, diffs_1] : [count_2, diffs_2]
-
diffs = count < count_3 ? diffs : diffs_3
-
end
-
-
# Compute the diff of two hashes or arrays
-
#
-
# @param [Array, Hash] obj1
-
# @param [Array, Hash] obj2
-
# @param [Hash] options the options to use when comparing
-
# * :strict (Boolean) [true] whether numeric values will be compared on type as well as value. Set to false to allow comparing Fixnum, Float, BigDecimal to each other
-
# * :similarity (Numeric) [0.8] should be between (0, 1]. Meaningful if there are similar hashes in arrays. See {best_diff}.
-
# * :delimiter (String) ['.'] the delimiter used when returning nested key references
-
# * :numeric_tolerance (Numeric) [0] should be a positive numeric value. Value by which numeric differences must be greater than. By default, numeric values are compared exactly; with the :tolerance option, the difference between numeric values must be greater than the given value.
-
# * :strip (Boolean) [false] whether or not to call #strip on strings before comparing
-
#
-
# @yield [path, value1, value2] Optional block is used to compare each value, instead of default #==. If the block returns value other than true of false, then other specified comparison options will be used to do the comparison.
-
#
-
# @return [Array] an array of changes.
-
# e.g. [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']]
-
#
-
# @example
-
# a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
-
# b = {"a" => 1, "b" => {}}
-
#
-
# diff = HashDiff.diff(a, b)
-
# diff.should == [['-', 'b.b1', 1], ['-', 'b.b2', 2]]
-
#
-
# @since 0.0.1
-
1
def self.diff(obj1, obj2, options = {}, &block)
-
opts = {
-
:prefix => '',
-
:similarity => 0.8,
-
:delimiter => '.',
-
:strict => true,
-
:strip => false,
-
:numeric_tolerance => 0
-
}.merge!(options)
-
-
opts[:comparison] = block if block_given?
-
-
# prefer to compare with provided block
-
result = custom_compare(opts[:comparison], opts[:prefix], obj1, obj2)
-
return result if result
-
-
if obj1.nil? and obj2.nil?
-
return []
-
end
-
-
if obj1.nil?
-
return [['~', opts[:prefix], nil, obj2]]
-
end
-
-
if obj2.nil?
-
return [['~', opts[:prefix], obj1, nil]]
-
end
-
-
unless comparable?(obj1, obj2, opts[:strict])
-
return [['~', opts[:prefix], obj1, obj2]]
-
end
-
-
result = []
-
if obj1.is_a?(Array)
-
changeset = diff_array(obj1, obj2, opts) do |lcs|
-
# use a's index for similarity
-
lcs.each do |pair|
-
result.concat(diff(obj1[pair[0]], obj2[pair[1]], opts.merge(:prefix => "#{opts[:prefix]}[#{pair[0]}]")))
-
end
-
end
-
-
changeset.each do |change|
-
if change[0] == '-'
-
result << ['-', "#{opts[:prefix]}[#{change[1]}]", change[2]]
-
elsif change[0] == '+'
-
result << ['+', "#{opts[:prefix]}[#{change[1]}]", change[2]]
-
end
-
end
-
elsif obj1.is_a?(Hash)
-
if opts[:prefix].empty?
-
prefix = ""
-
else
-
prefix = "#{opts[:prefix]}#{opts[:delimiter]}"
-
end
-
-
deleted_keys = obj1.keys - obj2.keys
-
common_keys = obj1.keys & obj2.keys
-
added_keys = obj2.keys - obj1.keys
-
-
# add deleted properties
-
deleted_keys.sort.each do |k|
-
custom_result = custom_compare(opts[:comparison], "#{prefix}#{k}", obj1[k], nil)
-
-
if custom_result
-
result.concat(custom_result)
-
else
-
result << ['-', "#{prefix}#{k}", obj1[k]]
-
end
-
end
-
-
# recursive comparison for common keys
-
common_keys.sort.each {|k| result.concat(diff(obj1[k], obj2[k], opts.merge(:prefix => "#{prefix}#{k}"))) }
-
-
# added properties
-
added_keys.sort.each do |k|
-
unless obj1.key?(k)
-
custom_result = custom_compare(opts[:comparison], "#{prefix}#{k}", nil, obj2[k])
-
-
if custom_result
-
result.concat(custom_result)
-
else
-
result << ['+', "#{prefix}#{k}", obj2[k]]
-
end
-
end
-
end
-
else
-
return [] if compare_values(obj1, obj2, opts)
-
return [['~', opts[:prefix], obj1, obj2]]
-
end
-
-
result
-
end
-
-
# @private
-
#
-
# diff array using LCS algorithm
-
1
def self.diff_array(a, b, options = {})
-
opts = {
-
:prefix => '',
-
:similarity => 0.8,
-
:delimiter => '.'
-
}.merge!(options)
-
-
change_set = []
-
if a.size == 0 and b.size == 0
-
return []
-
elsif a.size == 0
-
b.each_index do |index|
-
change_set << ['+', index, b[index]]
-
end
-
return change_set
-
elsif b.size == 0
-
a.each_index do |index|
-
i = a.size - index - 1
-
change_set << ['-', i, a[i]]
-
end
-
return change_set
-
end
-
-
links = lcs(a, b, opts)
-
-
# yield common
-
yield links if block_given?
-
-
# padding the end
-
links << [a.size, b.size]
-
-
last_x = -1
-
last_y = -1
-
links.each do |pair|
-
x, y = pair
-
-
# remove from a, beginning from the end
-
(x > last_x + 1) and (x - last_x - 2).downto(0).each do |i|
-
change_set << ['-', last_y + i + 1, a[i + last_x + 1]]
-
end
-
-
# add from b, beginning from the head
-
(y > last_y + 1) and 0.upto(y - last_y - 2).each do |i|
-
change_set << ['+', last_y + i + 1, b[i + last_y + 1]]
-
end
-
-
# update flags
-
last_x = x
-
last_y = y
-
end
-
-
change_set
-
end
-
-
end
-
1
module HashDiff
-
# @private
-
#
-
# caculate array difference using LCS algorithm
-
# http://en.wikipedia.org/wiki/Longest_common_subsequence_problem
-
1
def self.lcs(a, b, options = {})
-
opts = { :similarity => 0.8 }.merge!(options)
-
-
opts[:prefix] = "#{opts[:prefix]}[*]"
-
-
return [] if a.size == 0 or b.size == 0
-
-
a_start = b_start = 0
-
a_finish = a.size - 1
-
b_finish = b.size - 1
-
vector = []
-
-
lcs = []
-
(b_start..b_finish).each do |bi|
-
lcs[bi] = []
-
(a_start..a_finish).each do |ai|
-
if similar?(a[ai], b[bi], opts)
-
topleft = (ai > 0 and bi > 0)? lcs[bi-1][ai-1][1] : 0
-
lcs[bi][ai] = [:topleft, topleft + 1]
-
elsif
-
top = (bi > 0)? lcs[bi-1][ai][1] : 0
-
left = (ai > 0)? lcs[bi][ai-1][1] : 0
-
count = (top > left) ? top : left
-
-
direction = :both
-
if top > left
-
direction = :top
-
elsif top < left
-
direction = :left
-
else
-
if bi == 0
-
direction = :top
-
elsif ai == 0
-
direction = :left
-
else
-
direction = :both
-
end
-
end
-
-
lcs[bi][ai] = [direction, count]
-
end
-
end
-
end
-
-
x = a_finish
-
y = b_finish
-
while x >= 0 and y >= 0 and lcs[y][x][1] > 0
-
if lcs[y][x][0] == :both
-
x -= 1
-
elsif lcs[y][x][0] == :topleft
-
vector.insert(0, [x, y])
-
x -= 1
-
y -= 1
-
elsif lcs[y][x][0] == :top
-
y -= 1
-
elsif lcs[y][x][0] == :left
-
x -= 1
-
end
-
end
-
-
vector
-
end
-
-
end
-
#
-
# This module provides methods to diff two hash, patch and unpatch hash
-
#
-
1
module HashDiff
-
-
# Apply patch to object
-
#
-
# @param [Hash, Array] obj the object to be patched, can be an Array or a Hash
-
# @param [Array] changes e.g. [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']]
-
# @param [Hash] options supports following keys:
-
# * :delimiter (String) ['.'] delimiter string for representing nested keys in changes array
-
#
-
# @return the object after patch
-
#
-
# @since 0.0.1
-
1
def self.patch!(obj, changes, options = {})
-
delimiter = options[:delimiter] || '.'
-
-
changes.each do |change|
-
parts = decode_property_path(change[1], delimiter)
-
last_part = parts.last
-
-
parent_node = node(obj, parts[0, parts.size-1])
-
-
if change[0] == '+'
-
if last_part.is_a?(Fixnum)
-
parent_node.insert(last_part, change[2])
-
else
-
parent_node[last_part] = change[2]
-
end
-
elsif change[0] == '-'
-
if last_part.is_a?(Fixnum)
-
parent_node.delete_at(last_part)
-
else
-
parent_node.delete(last_part)
-
end
-
elsif change[0] == '~'
-
parent_node[last_part] = change[3]
-
end
-
end
-
-
obj
-
end
-
-
# Unpatch an object
-
#
-
# @param [Hash, Array] obj the object to be unpatched, can be an Array or a Hash
-
# @param [Array] changes e.g. [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']]
-
# @param [Hash] options supports following keys:
-
# * :delimiter (String) ['.'] delimiter string for representing nested keys in changes array
-
#
-
# @return the object after unpatch
-
#
-
# @since 0.0.1
-
1
def self.unpatch!(obj, changes, options = {})
-
delimiter = options[:delimiter] || '.'
-
-
changes.reverse_each do |change|
-
parts = decode_property_path(change[1], delimiter)
-
last_part = parts.last
-
-
parent_node = node(obj, parts[0, parts.size-1])
-
-
if change[0] == '+'
-
if last_part.is_a?(Fixnum)
-
parent_node.delete_at(last_part)
-
else
-
parent_node.delete(last_part)
-
end
-
elsif change[0] == '-'
-
if last_part.is_a?(Fixnum)
-
parent_node.insert(last_part, change[2])
-
else
-
parent_node[last_part] = change[2]
-
end
-
elsif change[0] == '~'
-
parent_node[last_part] = change[2]
-
end
-
end
-
-
obj
-
end
-
-
end
-
1
module HashDiff
-
-
# @private
-
#
-
# judge whether two objects are similar
-
1
def self.similar?(a, b, options = {})
-
opts = { :similarity => 0.8 }.merge(options)
-
-
count_a = count_nodes(a)
-
count_b = count_nodes(b)
-
diffs = count_diff diff(a, b, opts)
-
-
if count_a + count_b == 0
-
return true
-
else
-
(1 - diffs.to_f/(count_a + count_b).to_f) >= opts[:similarity]
-
end
-
end
-
-
# @private
-
#
-
# count node differences
-
1
def self.count_diff(diffs)
-
diffs.inject(0) do |sum, item|
-
old_change_count = count_nodes(item[2])
-
new_change_count = count_nodes(item[3])
-
sum += (old_change_count + new_change_count)
-
end
-
end
-
-
# @private
-
#
-
# count total nodes for an object
-
1
def self.count_nodes(obj)
-
return 0 unless obj
-
-
count = 0
-
if obj.is_a?(Array)
-
obj.each {|e| count += count_nodes(e) }
-
elsif obj.is_a?(Hash)
-
obj.each {|k, v| count += count_nodes(v) }
-
else
-
return 1
-
end
-
-
count
-
end
-
-
# @private
-
#
-
# decode property path into an array
-
# @param [String] path Property-string
-
# @param [String] delimiter Property-string delimiter
-
#
-
# e.g. "a.b[3].c" => ['a', 'b', 3, 'c']
-
1
def self.decode_property_path(path, delimiter='.')
-
parts = path.split(delimiter).collect do |part|
-
if part =~ /^(\w*)\[(\d+)\]$/
-
if $1.size > 0
-
[$1, $2.to_i]
-
else
-
$2.to_i
-
end
-
else
-
part
-
end
-
end
-
-
parts.flatten
-
end
-
-
# @private
-
#
-
# get the node of hash by given path parts
-
1
def self.node(hash, parts)
-
temp = hash
-
parts.each do |part|
-
temp = temp[part]
-
end
-
temp
-
end
-
-
# @private
-
#
-
# check for equality or "closeness" within given tolerance
-
1
def self.compare_values(obj1, obj2, options = {})
-
if (options[:numeric_tolerance].is_a? Numeric) &&
-
[obj1, obj2].all? { |v| v.is_a? Numeric }
-
return (obj1 - obj2).abs <= options[:numeric_tolerance]
-
end
-
-
if options[:strip] == true
-
obj1 = obj1.strip if obj1.respond_to?(:strip)
-
obj2 = obj2.strip if obj2.respond_to?(:strip)
-
end
-
-
if options[:case_insensitive] == true
-
obj1 = obj1.downcase if obj1.respond_to?(:downcase)
-
obj2 = obj2.downcase if obj2.respond_to?(:downcase)
-
end
-
-
obj1 == obj2
-
end
-
-
# @private
-
#
-
# check if objects are comparable
-
1
def self.comparable?(obj1, obj2, strict = true)
-
[Array, Hash].each do |type|
-
return true if obj1.is_a?(type) && obj2.is_a?(type)
-
end
-
return true if !strict && obj1.is_a?(Numeric) && obj2.is_a?(Numeric)
-
obj1.is_a?(obj2.class) && obj2.is_a?(obj1.class)
-
end
-
-
# @private
-
#
-
# try custom comparison
-
1
def self.custom_compare(method, key, obj1, obj2)
-
if method
-
res = method.call(key, obj1, obj2)
-
-
# nil != false here
-
if res == false
-
return [['~', key, obj1, obj2]]
-
elsif res == true
-
return []
-
end
-
end
-
end
-
end
-
1
module HashDiff
-
1
VERSION = '0.3.0'
-
end
-
1
require 'pathname'
-
1
require 'net/http'
-
1
require 'net/https'
-
1
require 'uri'
-
1
require 'zlib'
-
1
require 'multi_xml'
-
1
require 'json'
-
1
require 'csv'
-
1
require 'erb'
-
-
1
require 'httparty/module_inheritable_attributes'
-
1
require 'httparty/cookie_hash'
-
1
require 'httparty/net_digest_auth'
-
1
require 'httparty/version'
-
1
require 'httparty/connection_adapter'
-
1
require 'httparty/logger/logger'
-
-
# @see HTTParty::ClassMethods
-
1
module HTTParty
-
1
def self.included(base)
-
2
base.extend ClassMethods
-
2
base.send :include, ModuleInheritableAttributes
-
2
base.send(:mattr_inheritable, :default_options)
-
2
base.send(:mattr_inheritable, :default_cookies)
-
2
base.instance_variable_set("@default_options", {})
-
2
base.instance_variable_set("@default_cookies", CookieHash.new)
-
end
-
-
# == Common Request Options
-
# Request methods (get, post, patch, put, delete, head, options) all take a common set of options. These are:
-
#
-
# [:+body+:] Body of the request. If passed an object that responds to #to_hash, will try to normalize it first, by default passing it to ActiveSupport::to_params. Any other kind of object will get used as-is.
-
# [:+http_proxyaddr+:] Address of proxy server to use.
-
# [:+http_proxyport+:] Port of proxy server to use.
-
# [:+http_proxyuser+:] User for proxy server authentication.
-
# [:+http_proxypass+:] Password for proxy server authentication.
-
# [:+limit+:] Maximum number of redirects to follow. Takes precedences over :+no_follow+.
-
# [:+query+:] Query string, or an object that responds to #to_hash representing it. Normalized according to the same rules as :+body+. If you specify this on a POST, you must use an object which responds to #to_hash. See also HTTParty::ClassMethods.default_params.
-
# [:+timeout+:] Timeout for opening connection and reading data.
-
# [:+local_host:] Local address to bind to before connecting.
-
# [:+local_port:] Local port to bind to before connecting.
-
# [:+body_steam:] Allow streaming to a REST server to specify a body_stream.
-
# [:+stream_body:] Allow for streaming large files without loading them into memory.
-
#
-
# There are also another set of options with names corresponding to various class methods. The methods in question are those that let you set a class-wide default, and the options override the defaults on a request-by-request basis. Those options are:
-
# * :+base_uri+: see HTTParty::ClassMethods.base_uri.
-
# * :+basic_auth+: see HTTParty::ClassMethods.basic_auth. Only one of :+basic_auth+ and :+digest_auth+ can be used at a time; if you try using both, you'll get an ArgumentError.
-
# * :+debug_output+: see HTTParty::ClassMethods.debug_output.
-
# * :+digest_auth+: see HTTParty::ClassMethods.digest_auth. Only one of :+basic_auth+ and :+digest_auth+ can be used at a time; if you try using both, you'll get an ArgumentError.
-
# * :+format+: see HTTParty::ClassMethods.format.
-
# * :+headers+: see HTTParty::ClassMethods.headers. Must be a an object which responds to #to_hash.
-
# * :+maintain_method_across_redirects+: see HTTParty::ClassMethods.maintain_method_across_redirects.
-
# * :+no_follow+: see HTTParty::ClassMethods.no_follow.
-
# * :+parser+: see HTTParty::ClassMethods.parser.
-
# * :+uri_adapter+: see HTTParty::ClassMethods.uri_adapter
-
# * :+connection_adapter+: see HTTParty::ClassMethods.connection_adapter.
-
# * :+pem+: see HTTParty::ClassMethods.pem.
-
# * :+query_string_normalizer+: see HTTParty::ClassMethods.query_string_normalizer
-
# * :+ssl_ca_file+: see HTTParty::ClassMethods.ssl_ca_file.
-
# * :+ssl_ca_path+: see HTTParty::ClassMethods.ssl_ca_path.
-
-
1
module ClassMethods
-
# Turns on logging
-
#
-
# class Foo
-
# include HTTParty
-
# logger Logger.new('http_logger'), :info, :apache
-
# end
-
1
def logger(logger, level = :info, format = :apache)
-
default_options[:logger] = logger
-
default_options[:log_level] = level
-
default_options[:log_format] = format
-
end
-
-
# Allows setting http proxy information to be used
-
#
-
# class Foo
-
# include HTTParty
-
# http_proxy 'http://foo.com', 80, 'user', 'pass'
-
# end
-
1
def http_proxy(addr = nil, port = nil, user = nil, pass = nil)
-
default_options[:http_proxyaddr] = addr
-
default_options[:http_proxyport] = port
-
default_options[:http_proxyuser] = user
-
default_options[:http_proxypass] = pass
-
end
-
-
# Allows setting a base uri to be used for each request.
-
# Will normalize uri to include http, etc.
-
#
-
# class Foo
-
# include HTTParty
-
# base_uri 'twitter.com'
-
# end
-
1
def base_uri(uri = nil)
-
1
return default_options[:base_uri] unless uri
-
1
default_options[:base_uri] = HTTParty.normalize_base_uri(uri)
-
end
-
-
# Allows setting basic authentication username and password.
-
#
-
# class Foo
-
# include HTTParty
-
# basic_auth 'username', 'password'
-
# end
-
1
def basic_auth(u, p)
-
default_options[:basic_auth] = {username: u, password: p}
-
end
-
-
# Allows setting digest authentication username and password.
-
#
-
# class Foo
-
# include HTTParty
-
# digest_auth 'username', 'password'
-
# end
-
1
def digest_auth(u, p)
-
default_options[:digest_auth] = {username: u, password: p}
-
end
-
-
# Do not send rails style query strings.
-
# Specically, don't use bracket notation when sending an array
-
#
-
# For a query:
-
# get '/', query: {selected_ids: [1,2,3]}
-
#
-
# The default query string looks like this:
-
# /?selected_ids[]=1&selected_ids[]=2&selected_ids[]=3
-
#
-
# Call `disable_rails_query_string_format` to transform the query string
-
# into:
-
# /?selected_ids=1&selected_ids=2&selected_ids=3
-
#
-
# @example
-
# class Foo
-
# include HTTParty
-
# disable_rails_query_string_format
-
# end
-
1
def disable_rails_query_string_format
-
query_string_normalizer Request::NON_RAILS_QUERY_STRING_NORMALIZER
-
end
-
-
# Allows setting default parameters to be appended to each request.
-
# Great for api keys and such.
-
#
-
# class Foo
-
# include HTTParty
-
# default_params api_key: 'secret', another: 'foo'
-
# end
-
1
def default_params(h = {})
-
raise ArgumentError, 'Default params must an object which respond to #to_hash' unless h.respond_to?(:to_hash)
-
default_options[:default_params] ||= {}
-
default_options[:default_params].merge!(h)
-
end
-
-
# Allows setting a default timeout for all HTTP calls
-
# Timeout is specified in seconds.
-
#
-
# class Foo
-
# include HTTParty
-
# default_timeout 10
-
# end
-
1
def default_timeout(t)
-
raise ArgumentError, 'Timeout must be an integer or float' unless t && (t.is_a?(Integer) || t.is_a?(Float))
-
default_options[:timeout] = t
-
end
-
-
# Allows setting a default open_timeout for all HTTP calls in seconds
-
#
-
# class Foo
-
# include HTTParty
-
# open_timeout 10
-
# end
-
1
def open_timeout(t)
-
raise ArgumentError, 'open_timeout must be an integer or float' unless t && (t.is_a?(Integer) || t.is_a?(Float))
-
default_options[:open_timeout] = t
-
end
-
-
# Allows setting a default read_timeout for all HTTP calls in seconds
-
#
-
# class Foo
-
# include HTTParty
-
# read_timeout 10
-
# end
-
1
def read_timeout(t)
-
raise ArgumentError, 'read_timeout must be an integer or float' unless t && (t.is_a?(Integer) || t.is_a?(Float))
-
default_options[:read_timeout] = t
-
end
-
-
# Set an output stream for debugging, defaults to $stderr.
-
# The output stream is passed on to Net::HTTP#set_debug_output.
-
#
-
# class Foo
-
# include HTTParty
-
# debug_output $stderr
-
# end
-
1
def debug_output(stream = $stderr)
-
default_options[:debug_output] = stream
-
end
-
-
# Allows setting HTTP headers to be used for each request.
-
#
-
# class Foo
-
# include HTTParty
-
# headers 'Accept' => 'text/html'
-
# end
-
1
def headers(h = {})
-
raise ArgumentError, 'Headers must an object which responds to #to_hash' unless h.respond_to?(:to_hash)
-
default_options[:headers] ||= {}
-
default_options[:headers].merge!(h.to_hash)
-
end
-
-
1
def cookies(h = {})
-
raise ArgumentError, 'Cookies must an object which respond to #to_hash' unless h.respond_to?(:to_hash)
-
default_cookies.add_cookies(h)
-
end
-
-
# Proceed to the location header when an HTTP response dictates a redirect.
-
# Redirects are always followed by default.
-
#
-
# @example
-
# class Foo
-
# include HTTParty
-
# base_uri 'http://google.com'
-
# follow_redirects true
-
# end
-
1
def follow_redirects(value = true)
-
default_options[:follow_redirects] = value
-
end
-
-
# Allows setting the format with which to parse.
-
# Must be one of the allowed formats ie: json, xml
-
#
-
# class Foo
-
# include HTTParty
-
# format :json
-
# end
-
1
def format(f = nil)
-
if f.nil?
-
default_options[:format]
-
else
-
parser(Parser) if parser.nil?
-
default_options[:format] = f
-
validate_format
-
end
-
end
-
-
# Declare whether or not to follow redirects. When true, an
-
# {HTTParty::RedirectionTooDeep} error will raise upon encountering a
-
# redirect. You can then gain access to the response object via
-
# HTTParty::RedirectionTooDeep#response.
-
#
-
# @see HTTParty::ResponseError#response
-
#
-
# @example
-
# class Foo
-
# include HTTParty
-
# base_uri 'http://google.com'
-
# no_follow true
-
# end
-
#
-
# begin
-
# Foo.get('/')
-
# rescue HTTParty::RedirectionTooDeep => e
-
# puts e.response.body
-
# end
-
1
def no_follow(value = false)
-
default_options[:no_follow] = value
-
end
-
-
# Declare that you wish to maintain the chosen HTTP method across redirects.
-
# The default behavior is to follow redirects via the GET method, except
-
# if you are making a HEAD request, in which case the default is to
-
# follow all redirects with HEAD requests.
-
# If you wish to maintain the original method, you can set this option to true.
-
#
-
# @example
-
# class Foo
-
# include HTTParty
-
# base_uri 'http://google.com'
-
# maintain_method_across_redirects true
-
# end
-
-
1
def maintain_method_across_redirects(value = true)
-
default_options[:maintain_method_across_redirects] = value
-
end
-
-
# Declare that you wish to resend the full HTTP request across redirects,
-
# even on redirects that should logically become GET requests.
-
# A 303 redirect in HTTP signifies that the redirected url should normally
-
# retrieved using a GET request, for instance, it is the output of a previous
-
# POST. maintain_method_across_redirects respects this behavior, but you
-
# can force HTTParty to resend_on_redirect even on 303 responses.
-
#
-
# @example
-
# class Foo
-
# include HTTParty
-
# base_uri 'http://google.com'
-
# resend_on_redirect
-
# end
-
-
1
def resend_on_redirect(value = true)
-
default_options[:resend_on_redirect] = value
-
end
-
-
# Allows setting a PEM file to be used
-
#
-
# class Foo
-
# include HTTParty
-
# pem File.read('/home/user/my.pem'), "optional password"
-
# end
-
1
def pem(pem_contents, password = nil)
-
default_options[:pem] = pem_contents
-
default_options[:pem_password] = password
-
end
-
-
# Allows setting a PKCS12 file to be used
-
#
-
# class Foo
-
# include HTTParty
-
# pkcs12 File.read('/home/user/my.p12'), "password"
-
# end
-
1
def pkcs12(p12_contents, password)
-
default_options[:p12] = p12_contents
-
default_options[:p12_password] = password
-
end
-
-
# Override the way query strings are normalized.
-
# Helpful for overriding the default rails normalization of Array queries.
-
#
-
# For a query:
-
# get '/', query: {selected_ids: [1,2,3]}
-
#
-
# The default query string normalizer returns:
-
# /?selected_ids[]=1&selected_ids[]=2&selected_ids[]=3
-
#
-
# Let's change it to this:
-
# /?selected_ids=1&selected_ids=2&selected_ids=3
-
#
-
# Pass a Proc to the query normalizer which accepts the yielded query.
-
#
-
# @example Modifying Array query strings
-
# class ServiceWrapper
-
# include HTTParty
-
#
-
# query_string_normalizer proc { |query|
-
# query.map do |key, value|
-
# value.map {|v| "#{key}=#{v}"}
-
# end.join('&')
-
# }
-
# end
-
#
-
# @param [Proc] normalizer custom query string normalizer.
-
# @yield [Hash, String] query string
-
# @yieldreturn [Array] an array that will later be joined with '&'
-
1
def query_string_normalizer(normalizer)
-
default_options[:query_string_normalizer] = normalizer
-
end
-
-
# Allows setting of SSL version to use. This only works in Ruby 1.9+.
-
# You can get a list of valid versions from OpenSSL::SSL::SSLContext::METHODS.
-
#
-
# class Foo
-
# include HTTParty
-
# ssl_version :SSLv3
-
# end
-
1
def ssl_version(version)
-
default_options[:ssl_version] = version
-
end
-
-
# Allows setting of SSL ciphers to use. This only works in Ruby 1.9+.
-
# You can get a list of valid specific ciphers from OpenSSL::Cipher.ciphers.
-
# You also can specify a cipher suite here, listed here at openssl.org:
-
# http://www.openssl.org/docs/apps/ciphers.html#CIPHER_SUITE_NAMES
-
#
-
# class Foo
-
# include HTTParty
-
# ciphers "RC4-SHA"
-
# end
-
1
def ciphers(cipher_names)
-
default_options[:ciphers] = cipher_names
-
end
-
-
# Allows setting an OpenSSL certificate authority file. The file
-
# should contain one or more certificates in PEM format.
-
#
-
# Setting this option enables certificate verification. All
-
# certificates along a chain must be available in ssl_ca_file or
-
# ssl_ca_path for verification to succeed.
-
#
-
#
-
# class Foo
-
# include HTTParty
-
# ssl_ca_file '/etc/ssl/certs/ca-certificates.crt'
-
# end
-
1
def ssl_ca_file(path)
-
default_options[:ssl_ca_file] = path
-
end
-
-
# Allows setting an OpenSSL certificate authority path (directory).
-
#
-
# Setting this option enables certificate verification. All
-
# certificates along a chain must be available in ssl_ca_file or
-
# ssl_ca_path for verification to succeed.
-
#
-
# class Foo
-
# include HTTParty
-
# ssl_ca_path '/etc/ssl/certs/'
-
# end
-
1
def ssl_ca_path(path)
-
default_options[:ssl_ca_path] = path
-
end
-
-
# Allows setting a custom parser for the response.
-
#
-
# class Foo
-
# include HTTParty
-
# parser Proc.new {|data| ...}
-
# end
-
1
def parser(custom_parser = nil)
-
if custom_parser.nil?
-
default_options[:parser]
-
else
-
default_options[:parser] = custom_parser
-
validate_format
-
end
-
end
-
-
# Allows setting a custom URI adapter.
-
#
-
# class Foo
-
# include HTTParty
-
# uri_adapter Addressable::URI
-
# end
-
1
def uri_adapter(uri_adapter)
-
raise ArgumentError, 'The URI adapter should respond to #parse' unless uri_adapter.respond_to?(:parse)
-
default_options[:uri_adapter] = uri_adapter
-
end
-
-
# Allows setting a custom connection_adapter for the http connections
-
#
-
# @example
-
# class Foo
-
# include HTTParty
-
# connection_adapter Proc.new {|uri, options| ... }
-
# end
-
#
-
# @example provide optional configuration for your connection_adapter
-
# class Foo
-
# include HTTParty
-
# connection_adapter Proc.new {|uri, options| ... }, {foo: :bar}
-
# end
-
#
-
# @see HTTParty::ConnectionAdapter
-
1
def connection_adapter(custom_adapter = nil, options = nil)
-
if custom_adapter.nil?
-
default_options[:connection_adapter]
-
else
-
default_options[:connection_adapter] = custom_adapter
-
default_options[:connection_adapter_options] = options
-
end
-
end
-
-
# Allows making a get request to a url.
-
#
-
# class Foo
-
# include HTTParty
-
# end
-
#
-
# # Simple get with full url
-
# Foo.get('http://foo.com/resource.json')
-
#
-
# # Simple get with full url and query parameters
-
# # ie: http://foo.com/resource.json?limit=10
-
# Foo.get('http://foo.com/resource.json', query: {limit: 10})
-
1
def get(path, options = {}, &block)
-
4
perform_request Net::HTTP::Get, path, options, &block
-
end
-
-
# Allows making a post request to a url.
-
#
-
# class Foo
-
# include HTTParty
-
# end
-
#
-
# # Simple post with full url and setting the body
-
# Foo.post('http://foo.com/resources', body: {bar: 'baz'})
-
#
-
# # Simple post with full url using :query option,
-
# # which gets set as form data on the request.
-
# Foo.post('http://foo.com/resources', query: {bar: 'baz'})
-
1
def post(path, options = {}, &block)
-
perform_request Net::HTTP::Post, path, options, &block
-
end
-
-
# Perform a PATCH request to a path
-
1
def patch(path, options = {}, &block)
-
perform_request Net::HTTP::Patch, path, options, &block
-
end
-
-
# Perform a PUT request to a path
-
1
def put(path, options = {}, &block)
-
perform_request Net::HTTP::Put, path, options, &block
-
end
-
-
# Perform a DELETE request to a path
-
1
def delete(path, options = {}, &block)
-
perform_request Net::HTTP::Delete, path, options, &block
-
end
-
-
# Perform a MOVE request to a path
-
1
def move(path, options = {}, &block)
-
perform_request Net::HTTP::Move, path, options, &block
-
end
-
-
# Perform a COPY request to a path
-
1
def copy(path, options = {}, &block)
-
perform_request Net::HTTP::Copy, path, options, &block
-
end
-
-
# Perform a HEAD request to a path
-
1
def head(path, options = {}, &block)
-
ensure_method_maintained_across_redirects options
-
perform_request Net::HTTP::Head, path, options, &block
-
end
-
-
# Perform an OPTIONS request to a path
-
1
def options(path, options = {}, &block)
-
perform_request Net::HTTP::Options, path, options, &block
-
end
-
-
1
attr_reader :default_options
-
-
1
private
-
-
1
def ensure_method_maintained_across_redirects(options)
-
unless options.has_key? :maintain_method_across_redirects
-
options[:maintain_method_across_redirects] = true
-
end
-
end
-
-
1
def perform_request(http_method, path, options, &block) #:nodoc:
-
4
options = ModuleInheritableAttributes.hash_deep_dup(default_options).merge(options)
-
4
process_headers(options)
-
4
process_cookies(options)
-
4
Request.new(http_method, path, options).perform(&block)
-
end
-
-
1
def process_headers(options)
-
4
if options[:headers] && headers.any?
-
options[:headers] = headers.merge(options[:headers])
-
end
-
end
-
-
1
def process_cookies(options) #:nodoc:
-
4
return unless options[:cookies] || default_cookies.any?
-
options[:headers] ||= headers.dup
-
options[:headers]["cookie"] = cookies.merge(options.delete(:cookies) || {}).to_cookie_string
-
end
-
-
1
def validate_format
-
if format && parser.respond_to?(:supports_format?) && !parser.supports_format?(format)
-
raise UnsupportedFormat, "'#{format.inspect}' Must be one of: #{parser.supported_formats.map(&:to_s).sort.join(', ')}"
-
end
-
end
-
end
-
-
1
def self.normalize_base_uri(url) #:nodoc:
-
1
normalized_url = url.dup
-
1
use_ssl = (normalized_url =~ /^https/) || (normalized_url =~ /:443\b/)
-
1
ends_with_slash = normalized_url =~ /\/$/
-
-
1
normalized_url.chop! if ends_with_slash
-
1
normalized_url.gsub!(/^https?:\/\//i, '')
-
-
1
"http#{'s' if use_ssl}://#{normalized_url}"
-
end
-
-
1
class Basement #:nodoc:
-
1
include HTTParty
-
end
-
-
1
def self.get(*args, &block)
-
Basement.get(*args, &block)
-
end
-
-
1
def self.post(*args, &block)
-
Basement.post(*args, &block)
-
end
-
-
1
def self.patch(*args, &block)
-
Basement.patch(*args, &block)
-
end
-
-
1
def self.put(*args, &block)
-
Basement.put(*args, &block)
-
end
-
-
1
def self.delete(*args, &block)
-
Basement.delete(*args, &block)
-
end
-
-
1
def self.move(*args, &block)
-
Basement.move(*args, &block)
-
end
-
-
1
def self.copy(*args, &block)
-
Basement.copy(*args, &block)
-
end
-
-
1
def self.head(*args, &block)
-
Basement.head(*args, &block)
-
end
-
-
1
def self.options(*args, &block)
-
Basement.options(*args, &block)
-
end
-
end
-
-
1
require 'httparty/hash_conversions'
-
1
require 'httparty/exceptions'
-
1
require 'httparty/parser'
-
1
require 'httparty/request'
-
1
require 'httparty/response'
-
1
module HTTParty
-
# Default connection adapter that returns a new Net::HTTP each time
-
#
-
# == Custom Connection Factories
-
#
-
# If you like to implement your own connection adapter, subclassing
-
# HTTPParty::ConnectionAdapter will make it easier. Just override
-
# the #connection method. The uri and options attributes will have
-
# all the info you need to construct your http connection. Whatever
-
# you return from your connection method needs to adhere to the
-
# Net::HTTP interface as this is what HTTParty expects.
-
#
-
# @example log the uri and options
-
# class LoggingConnectionAdapter < HTTParty::ConnectionAdapter
-
# def connection
-
# puts uri
-
# puts options
-
# Net::HTTP.new(uri)
-
# end
-
# end
-
#
-
# @example count number of http calls
-
# class CountingConnectionAdapter < HTTParty::ConnectionAdapter
-
# @@count = 0
-
#
-
# self.count
-
# @@count
-
# end
-
#
-
# def connection
-
# self.count += 1
-
# super
-
# end
-
# end
-
#
-
# === Configuration
-
# There is lots of configuration data available for your connection adapter
-
# in the #options attribute. It is up to you to interpret them within your
-
# connection adapter. Take a look at the implementation of
-
# HTTParty::ConnectionAdapter#connection for examples of how they are used.
-
# Some things that are probably interesting are as follows:
-
# * :+timeout+: timeout in seconds
-
# * :+open_timeout+: http connection open_timeout in seconds, overrides timeout if set
-
# * :+read_timeout+: http connection read_timeout in seconds, overrides timeout if set
-
# * :+debug_output+: see HTTParty::ClassMethods.debug_output.
-
# * :+pem+: contains pem data. see HTTParty::ClassMethods.pem.
-
# * :+verify+: verify the server’s certificate against the ca certificate.
-
# * :+verify_peer+: set to false to turn off server verification but still send client certificate
-
# * :+ssl_ca_file+: see HTTParty::ClassMethods.ssl_ca_file.
-
# * :+ssl_ca_path+: see HTTParty::ClassMethods.ssl_ca_path.
-
# * :+connection_adapter_options+: contains the hash you passed to HTTParty.connection_adapter when you configured your connection adapter
-
1
class ConnectionAdapter
-
# Private: Regex used to strip brackets from IPv6 URIs.
-
1
StripIpv6BracketsRegex = /\A\[(.*)\]\z/
-
-
# Public
-
1
def self.call(uri, options)
-
4
new(uri, options).connection
-
end
-
-
1
attr_reader :uri, :options
-
-
1
def initialize(uri, options = {})
-
4
uri_adapter = options[:uri_adapter] || URI
-
4
raise ArgumentError, "uri must be a #{uri_adapter}, not a #{uri.class}" unless uri.is_a? uri_adapter
-
-
4
@uri = uri
-
4
@options = options
-
end
-
-
1
def connection
-
4
host = clean_host(uri.host)
-
4
port = uri.port || (uri.scheme == 'https' ? 443 : 80)
-
4
if options[:http_proxyaddr]
-
http = Net::HTTP.new(host, port, options[:http_proxyaddr], options[:http_proxyport], options[:http_proxyuser], options[:http_proxypass])
-
else
-
4
http = Net::HTTP.new(host, port)
-
end
-
-
4
http.use_ssl = ssl_implied?(uri)
-
-
4
attach_ssl_certificates(http, options)
-
-
4
if options[:timeout] && (options[:timeout].is_a?(Integer) || options[:timeout].is_a?(Float))
-
http.open_timeout = options[:timeout]
-
http.read_timeout = options[:timeout]
-
end
-
-
4
if options[:read_timeout] && (options[:read_timeout].is_a?(Integer) || options[:read_timeout].is_a?(Float))
-
http.read_timeout = options[:read_timeout]
-
end
-
-
4
if options[:open_timeout] && (options[:open_timeout].is_a?(Integer) || options[:open_timeout].is_a?(Float))
-
http.open_timeout = options[:open_timeout]
-
end
-
-
4
if options[:debug_output]
-
http.set_debug_output(options[:debug_output])
-
end
-
-
4
if options[:ciphers]
-
http.ciphers = options[:ciphers]
-
end
-
-
# Bind to a specific local address or port
-
#
-
# @see https://bugs.ruby-lang.org/issues/6617
-
4
if options[:local_host]
-
if RUBY_VERSION >= "2.0.0"
-
http.local_host = options[:local_host]
-
else
-
Kernel.warn("Warning: option :local_host requires Ruby version 2.0 or later")
-
end
-
end
-
-
4
if options[:local_port]
-
if RUBY_VERSION >= "2.0.0"
-
http.local_port = options[:local_port]
-
else
-
Kernel.warn("Warning: option :local_port requires Ruby version 2.0 or later")
-
end
-
end
-
-
4
http
-
end
-
-
1
private
-
-
1
def clean_host(host)
-
4
strip_ipv6_brackets(host)
-
end
-
-
1
def strip_ipv6_brackets(host)
-
4
StripIpv6BracketsRegex =~ host ? $1 : host
-
end
-
-
1
def ssl_implied?(uri)
-
4
uri.port == 443 || uri.scheme == 'https'
-
end
-
-
1
def attach_ssl_certificates(http, options)
-
4
if http.use_ssl?
-
if options.fetch(:verify, true)
-
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
-
if options[:cert_store]
-
http.cert_store = options[:cert_store]
-
else
-
# Use the default cert store by default, i.e. system ca certs
-
http.cert_store = OpenSSL::X509::Store.new
-
http.cert_store.set_default_paths
-
end
-
else
-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
-
end
-
-
# Client certificate authentication
-
# Note: options[:pem] must contain the content of a PEM file having the private key appended
-
if options[:pem]
-
http.cert = OpenSSL::X509::Certificate.new(options[:pem])
-
http.key = OpenSSL::PKey::RSA.new(options[:pem], options[:pem_password])
-
http.verify_mode = options[:verify_peer] == false ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
-
end
-
-
# PKCS12 client certificate authentication
-
if options[:p12]
-
p12 = OpenSSL::PKCS12.new(options[:p12], options[:p12_password])
-
http.cert = p12.certificate
-
http.key = p12.key
-
http.verify_mode = options[:verify_peer] == false ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
-
end
-
-
# SSL certificate authority file and/or directory
-
if options[:ssl_ca_file]
-
http.ca_file = options[:ssl_ca_file]
-
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
-
end
-
-
if options[:ssl_ca_path]
-
http.ca_path = options[:ssl_ca_path]
-
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
-
end
-
-
# This is only Ruby 1.9+
-
if options[:ssl_version] && http.respond_to?(:ssl_version=)
-
http.ssl_version = options[:ssl_version]
-
end
-
end
-
end
-
end
-
end
-
1
class HTTParty::CookieHash < Hash #:nodoc:
-
1
CLIENT_COOKIES = %w(path expires domain path secure httponly)
-
-
1
def add_cookies(value)
-
case value
-
when Hash
-
merge!(value)
-
when String
-
value.split('; ').each do |cookie|
-
array = cookie.split('=', 2)
-
self[array[0].to_sym] = array[1]
-
end
-
else
-
raise "add_cookies only takes a Hash or a String"
-
end
-
end
-
-
1
def to_cookie_string
-
delete_if { |k, v| CLIENT_COOKIES.include?(k.to_s.downcase) }.collect { |k, v| "#{k}=#{v}" }.join("; ")
-
end
-
end
-
1
module HTTParty
-
# @abstact Exceptions raised by HTTParty inherit from Error
-
1
class Error < StandardError; end
-
-
# Exception raised when you attempt to set a non-existant format
-
1
class UnsupportedFormat < Error; end
-
-
# Exception raised when using a URI scheme other than HTTP or HTTPS
-
1
class UnsupportedURIScheme < Error; end
-
-
# @abstract Exceptions which inherit from ResponseError contain the Net::HTTP
-
# response object accessible via the {#response} method.
-
1
class ResponseError < Error
-
# Returns the response of the last request
-
# @return [Net::HTTPResponse] A subclass of Net::HTTPResponse, e.g.
-
# Net::HTTPOK
-
1
attr_reader :response
-
-
# Instantiate an instance of ResponseError with a Net::HTTPResponse object
-
# @param [Net::HTTPResponse]
-
1
def initialize(response)
-
@response = response
-
end
-
end
-
-
# Exception that is raised when request has redirected too many times.
-
# Calling {#response} returns the Net:HTTP response object.
-
1
class RedirectionTooDeep < ResponseError; end
-
end
-
1
module HTTParty
-
1
module HashConversions
-
# @return <String> This hash as a query string
-
#
-
# @example
-
# { name: "Bob",
-
# address: {
-
# street: '111 Ruby Ave.',
-
# city: 'Ruby Central',
-
# phones: ['111-111-1111', '222-222-2222']
-
# }
-
# }.to_params
-
# #=> "name=Bob&address[city]=Ruby Central&address[phones][]=111-111-1111&address[phones][]=222-222-2222&address[street]=111 Ruby Ave."
-
1
def self.to_params(hash)
-
42
hash.to_hash.map { |k, v| normalize_param(k, v) }.join.chop
-
end
-
-
# @param key<Object> The key for the param.
-
# @param value<Object> The value for the param.
-
#
-
# @return <String> This key value pair as a param
-
#
-
# @example normalize_param(:name, "Bob Jones") #=> "name=Bob%20Jones&"
-
1
def self.normalize_param(key, value)
-
34
param = ''
-
34
stack = []
-
-
34
if value.respond_to?(:to_ary)
-
param << value.to_ary.map { |element| normalize_param("#{key}[]", element) }.join
-
elsif value.respond_to?(:to_hash)
-
stack << [key, value.to_hash]
-
else
-
34
param << "#{key}=#{ERB::Util.url_encode(value.to_s)}&"
-
end
-
-
34
stack.each do |parent, hash|
-
hash.each do |k, v|
-
if v.respond_to?(:to_hash)
-
stack << ["#{parent}[#{k}]", v.to_hash]
-
else
-
param << normalize_param("#{parent}[#{k}]", v)
-
end
-
end
-
end
-
-
34
param
-
end
-
end
-
end
-
1
module HTTParty
-
1
module Logger
-
1
class ApacheFormatter #:nodoc:
-
1
TAG_NAME = HTTParty.name
-
-
1
attr_accessor :level, :logger, :current_time
-
-
1
def initialize(logger, level)
-
@logger = logger
-
@level = level.to_sym
-
end
-
-
1
def format(request, response)
-
current_time = Time.now.strftime("%Y-%m-%d %H:%M:%S %z")
-
http_method = request.http_method.name.split("::").last.upcase
-
path = request.path.to_s
-
content_length = response.respond_to?(:headers) ? response.headers['Content-Length'] : response['Content-Length']
-
@logger.send @level, "[#{TAG_NAME}] [#{current_time}] #{response.code} \"#{http_method} #{path}\" #{content_length || '-'} "
-
end
-
end
-
end
-
end
-
1
module HTTParty
-
1
module Logger
-
1
class CurlFormatter #:nodoc:
-
1
TAG_NAME = HTTParty.name
-
1
OUT = ">"
-
1
IN = "<"
-
-
1
attr_accessor :level, :logger, :current_time
-
-
1
def initialize(logger, level)
-
@logger = logger
-
@level = level.to_sym
-
end
-
-
1
def format(request, response)
-
messages = []
-
time = Time.now.strftime("%Y-%m-%d %H:%M:%S %z")
-
http_method = request.http_method.name.split("::").last.upcase
-
path = request.path.to_s
-
-
messages << print(time, OUT, "#{http_method} #{path}")
-
-
if request.options[:headers] && request.options[:headers].size > 0
-
request.options[:headers].each do |k, v|
-
messages << print(time, OUT, "#{k}: #{v}")
-
end
-
end
-
-
messages << print(time, OUT, request.raw_body)
-
messages << print(time, OUT, "")
-
messages << print(time, IN, "HTTP/#{response.http_version} #{response.code}")
-
-
headers = response.respond_to?(:headers) ? response.headers : response
-
response.each_header do |response_header|
-
messages << print(time, IN, "#{response_header.capitalize}: #{headers[response_header]}")
-
end
-
-
messages << print(time, IN, "\n#{response.body}")
-
-
@logger.send @level, messages.join("\n")
-
end
-
-
1
def print(time, direction, line)
-
"[#{TAG_NAME}] [#{time}] #{direction} #{line}"
-
end
-
end
-
end
-
end
-
1
require 'httparty/logger/apache_formatter'
-
1
require 'httparty/logger/curl_formatter'
-
-
1
module HTTParty
-
1
module Logger
-
1
def self.formatters
-
@formatters ||= {
-
:curl => Logger::CurlFormatter,
-
:apache => Logger::ApacheFormatter
-
}
-
end
-
-
1
def self.add_formatter(name, formatter)
-
raise HTTParty::Error.new("Log Formatter with name #{name} already exists") if formatters.include?(name)
-
formatters.merge!(name.to_sym => formatter)
-
end
-
-
1
def self.build(logger, level, formatter)
-
level ||= :info
-
formatter ||= :apache
-
-
logger_klass = formatters[formatter] || Logger::ApacheFormatter
-
logger_klass.new(logger, level)
-
end
-
end
-
end
-
1
module HTTParty
-
1
module ModuleInheritableAttributes #:nodoc:
-
1
def self.included(base)
-
2
base.extend(ClassMethods)
-
end
-
-
# borrowed from Rails 3.2 ActiveSupport
-
1
def self.hash_deep_dup(hash)
-
4
duplicate = hash.dup
-
-
4
duplicate.each_pair do |key, value|
-
4
duplicate[key] = if value.is_a?(Hash)
-
hash_deep_dup(value)
-
elsif value.is_a?(Proc)
-
duplicate[key] = value.dup
-
else
-
4
value
-
end
-
end
-
-
4
duplicate
-
end
-
-
1
module ClassMethods #:nodoc:
-
1
def mattr_inheritable(*args)
-
4
@mattr_inheritable_attrs ||= [:mattr_inheritable_attrs]
-
4
@mattr_inheritable_attrs += args
-
-
4
args.each do |arg|
-
4
module_eval %(class << self; attr_accessor :#{arg} end)
-
end
-
-
4
@mattr_inheritable_attrs
-
end
-
-
1
def inherited(subclass)
-
super
-
@mattr_inheritable_attrs.each do |inheritable_attribute|
-
ivar = "@#{inheritable_attribute}"
-
subclass.instance_variable_set(ivar, instance_variable_get(ivar).clone)
-
-
if instance_variable_get(ivar).respond_to?(:merge)
-
method = <<-EOM
-
def self.#{inheritable_attribute}
-
duplicate = ModuleInheritableAttributes.hash_deep_dup(#{ivar})
-
#{ivar} = superclass.#{inheritable_attribute}.merge(duplicate)
-
end
-
EOM
-
-
subclass.class_eval method
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'digest/md5'
-
1
require 'net/http'
-
-
1
module Net
-
1
module HTTPHeader
-
1
def digest_auth(username, password, response)
-
authenticator = DigestAuthenticator.new(
-
username,
-
password,
-
@method,
-
@path,
-
response
-
)
-
-
@header['Authorization'] = authenticator.authorization_header
-
@header['cookie'] = append_cookies(authenticator) if response['Set-Cookie']
-
end
-
-
1
def append_cookies(authenticator)
-
cookies = @header['cookie'] ? @header['cookie'] : []
-
cookies.concat(authenticator.cookie_header)
-
end
-
-
1
class DigestAuthenticator
-
1
def initialize(username, password, method, path, response_header)
-
@username = username
-
@password = password
-
@method = method
-
@path = path
-
@response = parse(response_header)
-
@cookies = parse_cookies(response_header)
-
end
-
-
1
def authorization_header
-
@cnonce = md5(random)
-
header = [
-
%(Digest username="#{@username}"),
-
%(realm="#{@response['realm']}"),
-
%(nonce="#{@response['nonce']}"),
-
%(uri="#{@path}"),
-
%(response="#{request_digest}")
-
]
-
-
header << %(algorithm="#{@response['algorithm']}") if algorithm_present?
-
-
if qop_present?
-
fields = [
-
%(cnonce="#{@cnonce}"),
-
%(qop="#{@response['qop']}"),
-
"nc=00000001"
-
]
-
fields.each { |field| header << field }
-
end
-
-
header << %(opaque="#{@response['opaque']}") if opaque_present?
-
header
-
end
-
-
1
def cookie_header
-
@cookies
-
end
-
-
1
private
-
-
1
def parse(response_header)
-
header = response_header['www-authenticate']
-
.gsub(/qop=(auth(?:-int)?)/, 'qop="\\1"')
-
-
header =~ /Digest (.*)/
-
params = {}
-
non_quoted = $1.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }
-
non_quoted.gsub(/(\w+)=([^,]*)/) { params[$1] = $2 }
-
params
-
end
-
-
1
def parse_cookies(response_header)
-
return [] unless response_header['Set-Cookie']
-
-
cookies = response_header['Set-Cookie'].split('; ')
-
-
cookies.reduce([]) do |ret, cookie|
-
ret << cookie
-
ret
-
end
-
-
cookies
-
end
-
-
1
def opaque_present?
-
@response.key?('opaque') && !@response['opaque'].empty?
-
end
-
-
1
def qop_present?
-
@response.key?('qop') && !@response['qop'].empty?
-
end
-
-
1
def random
-
format "%x", (Time.now.to_i + rand(65535))
-
end
-
-
1
def request_digest
-
a = [md5(a1), @response['nonce'], md5(a2)]
-
a.insert(2, "00000001", @cnonce, @response['qop']) if qop_present?
-
md5(a.join(":"))
-
end
-
-
1
def md5(str)
-
Digest::MD5.hexdigest(str)
-
end
-
-
1
def algorithm_present?
-
@response.key?('algorithm') && !@response['algorithm'].empty?
-
end
-
-
1
def use_md5_sess?
-
algorithm_present? && @response['algorithm'] == 'MD5-sess'
-
end
-
-
1
def a1
-
a1_user_realm_pwd = [@username, @response['realm'], @password].join(':')
-
if use_md5_sess?
-
[ md5(a1_user_realm_pwd), @response['nonce'], @cnonce ].join(':')
-
else
-
a1_user_realm_pwd
-
end
-
end
-
-
1
def a2
-
[@method, @path].join(":")
-
end
-
end
-
end
-
end
-
1
module HTTParty
-
# The default parser used by HTTParty, supports xml, json, html, csv and
-
# plain text.
-
#
-
# == Custom Parsers
-
#
-
# If you'd like to do your own custom parsing, subclassing HTTParty::Parser
-
# will make that process much easier. There are a few different ways you can
-
# utilize HTTParty::Parser as a superclass.
-
#
-
# @example Intercept the parsing for all formats
-
# class SimpleParser < HTTParty::Parser
-
# def parse
-
# perform_parsing
-
# end
-
# end
-
#
-
# @example Add the atom format and parsing method to the default parser
-
# class AtomParsingIncluded < HTTParty::Parser
-
# SupportedFormats.merge!(
-
# {"application/atom+xml" => :atom}
-
# )
-
#
-
# def atom
-
# perform_atom_parsing
-
# end
-
# end
-
#
-
# @example Only support the atom format
-
# class ParseOnlyAtom < HTTParty::Parser
-
# SupportedFormats = {"application/atom+xml" => :atom}
-
#
-
# def atom
-
# perform_atom_parsing
-
# end
-
# end
-
#
-
# @abstract Read the Custom Parsers section for more information.
-
1
class Parser
-
1
SupportedFormats = {
-
'text/xml' => :xml,
-
'application/xml' => :xml,
-
'application/json' => :json,
-
'text/json' => :json,
-
'application/javascript' => :plain,
-
'text/javascript' => :plain,
-
'text/html' => :html,
-
'text/plain' => :plain,
-
'text/csv' => :csv,
-
'application/csv' => :csv,
-
'text/comma-separated-values' => :csv
-
}
-
-
# The response body of the request
-
# @return [String]
-
1
attr_reader :body
-
-
# The intended parsing format for the request
-
# @return [Symbol] e.g. :json
-
1
attr_reader :format
-
-
# Instantiate the parser and call {#parse}.
-
# @param [String] body the response body
-
# @param [Symbol] format the response format
-
# @return parsed response
-
1
def self.call(body, format)
-
4
new(body, format).parse
-
end
-
-
# @return [Hash] the SupportedFormats hash
-
1
def self.formats
-
12
const_get(:SupportedFormats)
-
end
-
-
# @param [String] mimetype response MIME type
-
# @return [Symbol]
-
# @return [nil] mime type not supported
-
1
def self.format_from_mimetype(mimetype)
-
16
formats[formats.keys.detect {|k| mimetype.include?(k)}]
-
end
-
-
# @return [Array<Symbol>] list of supported formats
-
1
def self.supported_formats
-
4
formats.values.uniq
-
end
-
-
# @param [Symbol] format e.g. :json, :xml
-
# @return [Boolean]
-
1
def self.supports_format?(format)
-
4
supported_formats.include?(format)
-
end
-
-
1
def initialize(body, format)
-
4
@body = body
-
4
@format = format
-
end
-
-
# @return [Object] the parsed body
-
# @return [nil] when the response body is nil, an empty string, spaces only or "null"
-
1
def parse
-
4
return nil if body.nil?
-
4
return nil if body == "null"
-
4
return nil if body.valid_encoding? && body.strip.empty?
-
4
if supports_format?
-
4
parse_supported_format
-
else
-
body
-
end
-
end
-
-
1
protected
-
-
1
def xml
-
MultiXml.parse(body)
-
end
-
-
1
def json
-
4
JSON.parse(body, :quirks_mode => true, :allow_nan => true)
-
end
-
-
1
def csv
-
CSV.parse(body)
-
end
-
-
1
def html
-
body
-
end
-
-
1
def plain
-
body
-
end
-
-
1
def supports_format?
-
4
self.class.supports_format?(format)
-
end
-
-
1
def parse_supported_format
-
4
send(format)
-
rescue NoMethodError => e
-
raise NotImplementedError, "#{self.class.name} has not implemented a parsing method for the #{format.inspect} format.", e.backtrace
-
end
-
end
-
end
-
1
module HTTParty
-
1
class Request #:nodoc:
-
1
SupportedHTTPMethods = [
-
Net::HTTP::Get,
-
Net::HTTP::Post,
-
Net::HTTP::Patch,
-
Net::HTTP::Put,
-
Net::HTTP::Delete,
-
Net::HTTP::Head,
-
Net::HTTP::Options,
-
Net::HTTP::Move,
-
Net::HTTP::Copy
-
]
-
-
1
SupportedURISchemes = ['http', 'https', 'webcal', nil]
-
-
1
NON_RAILS_QUERY_STRING_NORMALIZER = proc do |query|
-
Array(query).sort_by { |a| a[0].to_s }.map do |key, value|
-
if value.nil?
-
key.to_s
-
elsif value.respond_to?(:to_ary)
-
value.to_ary.map {|v| "#{key}=#{ERB::Util.url_encode(v.to_s)}"}
-
else
-
HashConversions.to_params(key => value)
-
end
-
end.flatten.join('&')
-
end
-
-
1
attr_accessor :http_method, :options, :last_response, :redirect, :last_uri
-
1
attr_reader :path
-
-
1
def initialize(http_method, path, o = {})
-
4
self.http_method = http_method
-
4
self.options = {
-
limit: o.delete(:no_follow) ? 1 : 5,
-
assume_utf16_is_big_endian: true,
-
default_params: {},
-
follow_redirects: true,
-
parser: Parser,
-
uri_adapter: URI,
-
connection_adapter: ConnectionAdapter
-
}.merge(o)
-
4
self.path = path
-
4
set_basic_auth_from_uri
-
end
-
-
1
def path=(uri)
-
4
uri_adapter = options[:uri_adapter]
-
-
4
@path = if uri.is_a?(uri_adapter)
-
uri
-
elsif String.try_convert(uri)
-
4
uri_adapter.parse uri
-
else
-
raise ArgumentError,
-
"bad argument (expected #{uri_adapter} object or URI string)"
-
end
-
end
-
-
1
def request_uri(uri)
-
4
if uri.respond_to? :request_uri
-
4
uri.request_uri
-
else
-
uri.path
-
end
-
end
-
-
1
def uri
-
8
if redirect && path.relative? && path.path[0] != "/"
-
last_uri_host = @last_uri.path.gsub(/[^\/]+$/, "")
-
-
path.path = "/#{path.path}" if last_uri_host[-1] != "/"
-
path.path = last_uri_host + path.path
-
end
-
-
8
new_uri = path.relative? ? options[:uri_adapter].parse("#{base_uri}#{path}") : path.clone
-
-
# avoid double query string on redirects [#12]
-
8
unless redirect
-
8
new_uri.query = query_string(new_uri)
-
end
-
-
8
unless SupportedURISchemes.include? new_uri.scheme
-
raise UnsupportedURIScheme, "'#{new_uri}' Must be HTTP, HTTPS or Generic"
-
end
-
-
8
@last_uri = new_uri
-
end
-
-
1
def base_uri
-
8
if redirect
-
base_uri = "#{@last_uri.scheme}://#{@last_uri.host}"
-
base_uri += ":#{@last_uri.port}" if @last_uri.port != 80
-
base_uri
-
else
-
8
options[:base_uri]
-
end
-
end
-
-
1
def format
-
4
options[:format] || (format_from_mimetype(last_response['content-type']) if last_response)
-
end
-
-
1
def parser
-
12
options[:parser]
-
end
-
-
1
def connection_adapter
-
4
options[:connection_adapter]
-
end
-
-
1
def perform(&block)
-
4
validate
-
4
setup_raw_request
-
4
chunked_body = nil
-
-
4
self.last_response = http.request(@raw_request) do |http_response|
-
4
if block
-
chunks = []
-
-
http_response.read_body do |fragment|
-
chunks << fragment unless options[:stream_body]
-
block.call(fragment)
-
end
-
-
chunked_body = chunks.join
-
end
-
end
-
-
4
handle_deflation unless http_method == Net::HTTP::Head
-
4
handle_response(chunked_body, &block)
-
end
-
-
1
def raw_body
-
@raw_request.body
-
end
-
-
1
private
-
-
1
def http
-
4
connection_adapter.call(uri, options)
-
end
-
-
1
def body
-
4
options[:body].respond_to?(:to_hash) ? normalize_query(options[:body]) : options[:body]
-
end
-
-
1
def credentials
-
(options[:basic_auth] || options[:digest_auth]).to_hash
-
end
-
-
1
def username
-
credentials[:username]
-
end
-
-
1
def password
-
credentials[:password]
-
end
-
-
1
def normalize_query(query)
-
8
if query_string_normalizer
-
query_string_normalizer.call(query)
-
else
-
8
HashConversions.to_params(query)
-
end
-
end
-
-
1
def query_string_normalizer
-
8
options[:query_string_normalizer]
-
end
-
-
1
def setup_raw_request
-
4
@raw_request = http_method.new(request_uri(uri))
-
4
@raw_request.body = body if body
-
4
@raw_request.body_stream = options[:body_stream] if options[:body_stream]
-
4
@raw_request.initialize_http_header(options[:headers].to_hash) if options[:headers].respond_to?(:to_hash)
-
4
@raw_request.basic_auth(username, password) if options[:basic_auth]
-
4
setup_digest_auth if options[:digest_auth]
-
end
-
-
1
def setup_digest_auth
-
auth_request = http_method.new(uri.request_uri)
-
auth_request.initialize_http_header(options[:headers].to_hash) if options[:headers].respond_to?(:to_hash)
-
res = http.request(auth_request)
-
-
if !res['www-authenticate'].nil? && res['www-authenticate'].length > 0
-
@raw_request.digest_auth(username, password, res)
-
end
-
end
-
-
1
def query_string(uri)
-
8
query_string_parts = []
-
8
query_string_parts << uri.query unless uri.query.nil?
-
-
8
if options[:query].respond_to?(:to_hash)
-
8
query_string_parts << normalize_query(options[:default_params].merge(options[:query].to_hash))
-
else
-
query_string_parts << normalize_query(options[:default_params]) unless options[:default_params].empty?
-
query_string_parts << options[:query] unless options[:query].nil?
-
end
-
-
8
query_string_parts.reject!(&:empty?) unless query_string_parts == [""]
-
8
query_string_parts.size > 0 ? query_string_parts.join('&') : nil
-
end
-
-
1
def get_charset
-
4
content_type = last_response["content-type"]
-
4
if content_type.nil?
-
return nil
-
end
-
-
4
if content_type =~ /;\s*charset\s*=\s*([^=,;"\s]+)/i
-
4
return $1
-
end
-
-
if content_type =~ /;\s*charset\s*=\s*"((\\.|[^\\"])+)"/i
-
return $1.gsub(/\\(.)/, '\1')
-
end
-
-
nil
-
end
-
-
1
def encode_with_ruby_encoding(body, charset)
-
4
encoding = Encoding.find(charset)
-
4
body.force_encoding(encoding)
-
rescue
-
body
-
end
-
-
1
def assume_utf16_is_big_endian
-
options[:assume_utf16_is_big_endian]
-
end
-
-
1
def encode_utf_16(body)
-
if body.bytesize >= 2
-
if body.getbyte(0) == 0xFF && body.getbyte(1) == 0xFE
-
return body.force_encoding("UTF-16LE")
-
elsif body.getbyte(0) == 0xFE && body.getbyte(1) == 0xFF
-
return body.force_encoding("UTF-16BE")
-
end
-
end
-
-
if assume_utf16_is_big_endian
-
body.force_encoding("UTF-16BE")
-
else
-
body.force_encoding("UTF-16LE")
-
end
-
end
-
-
1
def _encode_body(body)
-
4
charset = get_charset
-
-
4
if charset.nil?
-
return body
-
end
-
-
4
if "utf-16".casecmp(charset) == 0
-
encode_utf_16(body)
-
else
-
4
encode_with_ruby_encoding(body, charset)
-
end
-
end
-
-
1
def encode_body(body)
-
4
if "".respond_to?(:encoding)
-
4
_encode_body(body)
-
else
-
body
-
end
-
end
-
-
1
def handle_response(body, &block)
-
4
if response_redirects?
-
options[:limit] -= 1
-
if options[:logger]
-
logger = HTTParty::Logger.build(options[:logger], options[:log_level], options[:log_format])
-
logger.format(self, last_response)
-
end
-
self.path = last_response['location']
-
self.redirect = true
-
if last_response.class == Net::HTTPSeeOther
-
unless options[:maintain_method_across_redirects] && options[:resend_on_redirect]
-
self.http_method = Net::HTTP::Get
-
end
-
elsif last_response.code != '307' && last_response.code != '308'
-
unless options[:maintain_method_across_redirects]
-
self.http_method = Net::HTTP::Get
-
end
-
end
-
capture_cookies(last_response)
-
perform(&block)
-
else
-
4
body ||= last_response.body
-
4
body = encode_body(body)
-
8
Response.new(self, last_response, lambda { parse_response(body) }, body: body)
-
end
-
end
-
-
# Inspired by Ruby 1.9
-
1
def handle_deflation
-
4
case last_response["content-encoding"]
-
when "gzip", "x-gzip"
-
body_io = StringIO.new(last_response.body)
-
last_response.body.replace Zlib::GzipReader.new(body_io).read
-
last_response.delete('content-encoding')
-
when "deflate"
-
last_response.body.replace Zlib::Inflate.inflate(last_response.body)
-
last_response.delete('content-encoding')
-
end
-
end
-
-
1
def response_redirects?
-
4
case last_response
-
when Net::HTTPNotModified # 304
-
false
-
when Net::HTTPRedirection
-
options[:follow_redirects] && last_response.key?('location')
-
end
-
end
-
-
1
def parse_response(body)
-
4
parser.call(body, format)
-
end
-
-
1
def capture_cookies(response)
-
return unless response['Set-Cookie']
-
cookies_hash = HTTParty::CookieHash.new
-
cookies_hash.add_cookies(options[:headers].to_hash['Cookie']) if options[:headers] && options[:headers].to_hash['Cookie']
-
response.get_fields('Set-Cookie').each { |cookie| cookies_hash.add_cookies(cookie) }
-
options[:headers] ||= {}
-
options[:headers]['Cookie'] = cookies_hash.to_cookie_string
-
end
-
-
# Uses the HTTP Content-Type header to determine the format of the
-
# response It compares the MIME type returned to the types stored in the
-
# SupportedFormats hash
-
1
def format_from_mimetype(mimetype)
-
4
if mimetype && parser.respond_to?(:format_from_mimetype)
-
4
parser.format_from_mimetype(mimetype)
-
end
-
end
-
-
1
def validate
-
4
raise HTTParty::RedirectionTooDeep.new(last_response), 'HTTP redirects too deep' if options[:limit].to_i <= 0
-
4
raise ArgumentError, 'only get, post, patch, put, delete, head, and options methods are supported' unless SupportedHTTPMethods.include?(http_method)
-
4
raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].respond_to?(:to_hash)
-
4
raise ArgumentError, 'only one authentication method, :basic_auth or :digest_auth may be used at a time' if options[:basic_auth] && options[:digest_auth]
-
4
raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].respond_to?(:to_hash)
-
4
raise ArgumentError, ':digest_auth must be a hash' if options[:digest_auth] && !options[:digest_auth].respond_to?(:to_hash)
-
4
raise ArgumentError, ':query must be hash if using HTTP Post' if post? && !options[:query].nil? && !options[:query].respond_to?(:to_hash)
-
end
-
-
1
def post?
-
4
Net::HTTP::Post == http_method
-
end
-
-
1
def set_basic_auth_from_uri
-
4
if path.userinfo
-
username, password = path.userinfo.split(':')
-
options[:basic_auth] = {username: username, password: password}
-
end
-
end
-
end
-
end
-
1
module HTTParty
-
1
class Response < BasicObject
-
1
def self.underscore(string)
-
56
string.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z])([A-Z])/, '\1_\2').downcase
-
end
-
-
1
attr_reader :request, :response, :body, :headers
-
-
1
def initialize(request, response, parsed_block, options = {})
-
4
@request = request
-
4
@response = response
-
4
@body = options[:body] || response.body
-
4
@parsed_block = parsed_block
-
4
@headers = Headers.new(response.to_hash)
-
-
4
if request.options[:logger]
-
logger = ::HTTParty::Logger.build(request.options[:logger], request.options[:log_level], request.options[:log_format])
-
logger.format(request, self)
-
end
-
end
-
-
1
def parsed_response
-
8
@parsed_response ||= @parsed_block.call
-
end
-
-
1
def class
-
Response
-
end
-
-
1
def code
-
5
response.code.to_i
-
end
-
-
1
def tap
-
yield self
-
self
-
end
-
-
1
def inspect
-
inspect_id = ::Kernel::format "%x", (object_id * 2)
-
%(#<#{self.class}:0x#{inspect_id} parsed_response=#{parsed_response.inspect}, @response=#{response.inspect}, @headers=#{headers.inspect}>)
-
end
-
-
1
CODES_TO_OBJ = ::Net::HTTPResponse::CODE_CLASS_TO_OBJ.merge ::Net::HTTPResponse::CODE_TO_OBJ
-
-
1
CODES_TO_OBJ.each do |response_code, klass|
-
56
name = klass.name.sub("Net::HTTP", '')
-
56
define_method("#{underscore(name)}?") do
-
klass === response
-
end
-
end
-
-
# Support old multiple_choice? method from pre 2.0.0 era.
-
1
if ::RUBY_VERSION >= "2.0.0" && ::RUBY_PLATFORM != "java"
-
1
alias_method :multiple_choice?, :multiple_choices?
-
end
-
-
1
def respond_to?(name, include_all = false)
-
return true if [:request, :response, :parsed_response, :body, :headers].include?(name)
-
parsed_response.respond_to?(name, include_all) || response.respond_to?(name, include_all)
-
end
-
-
1
protected
-
-
1
def method_missing(name, *args, &block)
-
4
if parsed_response.respond_to?(name)
-
4
parsed_response.send(name, *args, &block)
-
elsif response.respond_to?(name)
-
response.send(name, *args, &block)
-
else
-
super
-
end
-
end
-
end
-
end
-
-
1
require 'httparty/response/headers'
-
1
module HTTParty
-
1
class Response #:nodoc:
-
1
class Headers
-
1
include ::Net::HTTPHeader
-
-
1
def initialize(header = {})
-
4
@header = header
-
end
-
-
1
def ==(other)
-
@header == other
-
end
-
-
1
def inspect
-
@header.inspect
-
end
-
-
1
def method_missing(name, *args, &block)
-
if @header.respond_to?(name)
-
@header.send(name, *args, &block)
-
else
-
super
-
end
-
end
-
-
1
def respond_to?(method, include_all = false)
-
super || @header.respond_to?(method, include_all)
-
end
-
end
-
end
-
end
-
1
module HTTParty
-
1
VERSION = "0.13.7"
-
end
-
1
require 'base64'
-
1
require 'bigdecimal'
-
1
require 'date'
-
1
require 'stringio'
-
1
require 'time'
-
1
require 'yaml'
-
-
1
module MultiXml
-
1
class ParseError < StandardError; end
-
1
class DisallowedTypeError < StandardError
-
1
def initialize(type)
-
super "Disallowed type attribute: #{type.inspect}"
-
end
-
end
-
-
REQUIREMENT_MAP = [
-
['ox', :ox],
-
['libxml', :libxml],
-
['nokogiri', :nokogiri],
-
['rexml/document', :rexml]
-
1
] unless defined?(REQUIREMENT_MAP)
-
-
1
CONTENT_ROOT = '__content__'.freeze unless defined?(CONTENT_ROOT)
-
-
1
unless defined?(PARSING)
-
1
PARSING = {
-
'symbol' => Proc.new{|symbol| symbol.to_sym},
-
'date' => Proc.new{|date| Date.parse(date)},
-
'datetime' => Proc.new{|time| Time.parse(time).utc rescue DateTime.parse(time).utc},
-
'integer' => Proc.new{|integer| integer.to_i},
-
'float' => Proc.new{|float| float.to_f},
-
'decimal' => Proc.new{|number| BigDecimal(number)},
-
'boolean' => Proc.new{|boolean| !%w(0 false).include?(boolean.strip)},
-
'string' => Proc.new{|string| string.to_s},
-
'yaml' => Proc.new{|yaml| YAML::load(yaml) rescue yaml},
-
'base64Binary' => Proc.new{|binary| ::Base64.decode64(binary)},
-
'binary' => Proc.new{|binary, entity| parse_binary(binary, entity)},
-
'file' => Proc.new{|file, entity| parse_file(file, entity)},
-
}
-
-
1
PARSING.update(
-
'double' => PARSING['float'],
-
'dateTime' => PARSING['datetime']
-
)
-
end
-
-
TYPE_NAMES = {
-
'Symbol' => 'symbol',
-
'Fixnum' => 'integer',
-
'Bignum' => 'integer',
-
'BigDecimal' => 'decimal',
-
'Float' => 'float',
-
'TrueClass' => 'boolean',
-
'FalseClass' => 'boolean',
-
'Date' => 'date',
-
'DateTime' => 'datetime',
-
'Time' => 'datetime',
-
'Array' => 'array',
-
'Hash' => 'hash'
-
1
} unless defined?(TYPE_NAMES)
-
-
1
DISALLOWED_XML_TYPES = %w(symbol yaml)
-
-
1
DEFAULT_OPTIONS = {
-
:typecast_xml_value => true,
-
:disallowed_types => DISALLOWED_XML_TYPES,
-
:symbolize_keys => false
-
}
-
-
1
class << self
-
# Get the current parser class.
-
1
def parser
-
return @parser if defined?(@parser)
-
self.parser = self.default_parser
-
@parser
-
end
-
-
# The default parser based on what you currently
-
# have loaded and installed. First checks to see
-
# if any parsers are already loaded, then checks
-
# to see which are installed if none are loaded.
-
1
def default_parser
-
return :ox if defined?(::Ox)
-
return :libxml if defined?(::LibXML)
-
return :nokogiri if defined?(::Nokogiri)
-
-
REQUIREMENT_MAP.each do |library, parser|
-
begin
-
require library
-
return parser
-
rescue LoadError
-
next
-
end
-
end
-
end
-
-
# Set the XML parser utilizing a symbol, string, or class.
-
# Supported by default are:
-
#
-
# * <tt>:libxml</tt>
-
# * <tt>:nokogiri</tt>
-
# * <tt>:ox</tt>
-
# * <tt>:rexml</tt>
-
1
def parser=(new_parser)
-
case new_parser
-
when String, Symbol
-
require "multi_xml/parsers/#{new_parser.to_s.downcase}"
-
@parser = MultiXml::Parsers.const_get("#{new_parser.to_s.split('_').map{|s| s.capitalize}.join('')}")
-
when Class, Module
-
@parser = new_parser
-
else
-
raise "Did not recognize your parser specification. Please specify either a symbol or a class."
-
end
-
end
-
-
# Parse an XML string or IO into Ruby.
-
#
-
# <b>Options</b>
-
#
-
# <tt>:symbolize_keys</tt> :: If true, will use symbols instead of strings for the keys.
-
#
-
# <tt>:disallowed_types</tt> :: Types to disallow from being typecasted. Defaults to `['yaml', 'symbol']`. Use `[]` to allow all types.
-
#
-
# <tt>:typecast_xml_value</tt> :: If true, won't typecast values for parsed document
-
1
def parse(xml, options={})
-
xml ||= ''
-
-
options = DEFAULT_OPTIONS.merge(options)
-
-
xml.strip! if xml.respond_to?(:strip!)
-
begin
-
xml = StringIO.new(xml) unless xml.respond_to?(:read)
-
-
char = xml.getc
-
return {} if char.nil?
-
xml.ungetc(char)
-
-
hash = undasherize_keys(parser.parse(xml) || {})
-
hash = options[:typecast_xml_value] ? typecast_xml_value(hash, options[:disallowed_types]) : hash
-
rescue DisallowedTypeError
-
raise
-
rescue parser.parse_error => error
-
raise ParseError, error.message, error.backtrace
-
end
-
hash = symbolize_keys(hash) if options[:symbolize_keys]
-
hash
-
end
-
-
# This module decorates files with the <tt>original_filename</tt>
-
# and <tt>content_type</tt> methods.
-
1
module FileLike #:nodoc:
-
1
attr_writer :original_filename, :content_type
-
-
1
def original_filename
-
@original_filename || 'untitled'
-
end
-
-
1
def content_type
-
@content_type || 'application/octet-stream'
-
end
-
end
-
-
1
private
-
-
# TODO: Add support for other encodings
-
1
def parse_binary(binary, entity) #:nodoc:
-
case entity['encoding']
-
when 'base64'
-
Base64.decode64(binary)
-
else
-
binary
-
end
-
end
-
-
1
def parse_file(file, entity)
-
f = StringIO.new(Base64.decode64(file))
-
f.extend(FileLike)
-
f.original_filename = entity['name']
-
f.content_type = entity['content_type']
-
f
-
end
-
-
1
def symbolize_keys(params)
-
case params
-
when Hash
-
params.inject({}) do |result, (key, value)|
-
result.merge(key.to_sym => symbolize_keys(value))
-
end
-
when Array
-
params.map{|value| symbolize_keys(value)}
-
else
-
params
-
end
-
end
-
-
1
def undasherize_keys(params)
-
case params
-
when Hash
-
params.inject({}) do |hash, (key, value)|
-
hash[key.to_s.tr('-', '_')] = undasherize_keys(value)
-
hash
-
end
-
when Array
-
params.map{|value| undasherize_keys(value)}
-
else
-
params
-
end
-
end
-
-
1
def typecast_xml_value(value, disallowed_types=nil)
-
disallowed_types ||= DISALLOWED_XML_TYPES
-
-
case value
-
when Hash
-
if value.include?('type') && !value['type'].is_a?(Hash) && disallowed_types.include?(value['type'])
-
raise DisallowedTypeError, value['type']
-
end
-
-
if value['type'] == 'array'
-
-
# this commented-out suggestion helps to avoid the multiple attribute
-
# problem, but it breaks when there is only one item in the array.
-
#
-
# from: https://github.com/jnunemaker/httparty/issues/102
-
#
-
# _, entries = value.detect { |k, v| k != 'type' && v.is_a?(Array) }
-
-
# This attempt fails to consider the order that the detect method
-
# retrieves the entries.
-
#_, entries = value.detect {|key, _| key != 'type'}
-
-
# This approach ignores attribute entries that are not convertable
-
# to an Array which allows attributes to be ignored.
-
_, entries = value.detect {|k, v| k != 'type' && (v.is_a?(Array) || v.is_a?(Hash)) }
-
-
if entries.nil? || (entries.is_a?(String) && entries.strip.empty?)
-
[]
-
else
-
case entries
-
when Array
-
entries.map {|entry| typecast_xml_value(entry, disallowed_types)}
-
when Hash
-
[typecast_xml_value(entries, disallowed_types)]
-
else
-
raise "can't typecast #{entries.class.name}: #{entries.inspect}"
-
end
-
end
-
elsif value.has_key?(CONTENT_ROOT)
-
content = value[CONTENT_ROOT]
-
if block = PARSING[value['type']]
-
if block.arity == 1
-
value.delete('type') if PARSING[value['type']]
-
if value.keys.size > 1
-
value[CONTENT_ROOT] = block.call(content)
-
value
-
else
-
block.call(content)
-
end
-
else
-
block.call(content, value)
-
end
-
else
-
value.keys.size > 1 ? value : content
-
end
-
elsif value['type'] == 'string' && value['nil'] != 'true'
-
''
-
# blank or nil parsed values are represented by nil
-
elsif value.empty? || value['nil'] == 'true'
-
nil
-
# If the type is the only element which makes it then
-
# this still makes the value nil, except if type is
-
# a XML node(where type['value'] is a Hash)
-
elsif value['type'] && value.size == 1 && !value['type'].is_a?(Hash)
-
nil
-
else
-
xml_value = value.inject({}) do |hash, (k, v)|
-
hash[k] = typecast_xml_value(v, disallowed_types)
-
hash
-
end
-
-
# Turn {:files => {:file => #<StringIO>} into {:files => #<StringIO>} so it is compatible with
-
# how multipart uploaded files from HTML appear
-
xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value
-
end
-
when Array
-
value.map!{|i| typecast_xml_value(i, disallowed_types)}
-
value.length > 1 ? value : value.first
-
when String
-
value
-
else
-
raise "can't typecast #{value.class.name}: #{value.inspect}"
-
end
-
end
-
end
-
-
end
-
#!/usr/bin/env ruby
-
-
1
require 'rspec/core'
-
1
RSpec::Core::Runner.invoke
-
# rubocop:disable Style/GlobalVars
-
1
$_rspec_core_load_started_at = Time.now
-
# rubocop:enable Style/GlobalVars
-
-
1
require "rspec/support"
-
1
RSpec::Support.require_rspec_support "caller_filter"
-
-
41
RSpec::Support.define_optimized_require_for_rspec(:core) { |f| require_relative f }
-
-
%w[
-
version
-
warnings
-
-
set
-
flat_map
-
filter_manager
-
dsl
-
notifications
-
reporter
-
-
hooks
-
memoized_helpers
-
metadata
-
metadata_filter
-
pending
-
formatters
-
ordering
-
-
world
-
configuration
-
option_parser
-
configuration_options
-
runner
-
example
-
shared_example_group
-
example_group
-
24
].each { |name| RSpec::Support.require_rspec_core name }
-
-
# Namespace for all core RSpec code.
-
1
module RSpec
-
1
autoload :SharedContext, 'rspec/core/shared_context'
-
-
1
extend RSpec::Core::Warnings
-
-
1
class << self
-
# Setters for shared global objects
-
# @api private
-
1
attr_writer :configuration, :world
-
end
-
-
# Used to ensure examples get reloaded and user configuration gets reset to
-
# defaults between multiple runs in the same process.
-
#
-
# Users must invoke this if they want to have the configuration reset when
-
# they use the runner multiple times within the same process. Users must deal
-
# themselves with re-configuration of RSpec before run.
-
1
def self.reset
-
@world = nil
-
@configuration = nil
-
end
-
-
# Used to ensure examples get reloaded between multiple runs in the same
-
# process and ensures user configuration is persisted.
-
#
-
# Users must invoke this if they want to clear all examples but preserve
-
# current configuration when they use the runner multiple times within the
-
# same process.
-
1
def self.clear_examples
-
world.reset
-
configuration.reporter.reset
-
configuration.start_time = ::RSpec::Core::Time.now
-
configuration.reset_filters
-
end
-
-
# Returns the global [Configuration](RSpec/Core/Configuration) object. While
-
# you _can_ use this method to access the configuration, the more common
-
# convention is to use [RSpec.configure](RSpec#configure-class_method).
-
#
-
# @example
-
# RSpec.configuration.drb_port = 1234
-
# @see RSpec.configure
-
# @see Core::Configuration
-
1
def self.configuration
-
326
@configuration ||= RSpec::Core::Configuration.new
-
end
-
1
configuration.expose_dsl_globally = true
-
-
# Yields the global configuration to a block.
-
# @yield [Configuration] global configuration
-
#
-
# @example
-
# RSpec.configure do |config|
-
# config.add_formatter 'documentation'
-
# end
-
# @see Core::Configuration
-
1
def self.configure
-
1
yield configuration if block_given?
-
end
-
-
# The example being executed.
-
#
-
# The primary audience for this method is library authors who need access
-
# to the example currently being executed and also want to support all
-
# versions of RSpec 2 and 3.
-
#
-
# @example
-
#
-
# RSpec.configure do |c|
-
# # context.example is deprecated, but RSpec.current_example is not
-
# # available until RSpec 3.0.
-
# fetch_current_example = RSpec.respond_to?(:current_example) ?
-
# proc { RSpec.current_example } : proc { |context| context.example }
-
#
-
# c.before(:example) do
-
# example = fetch_current_example.call(self)
-
#
-
# # ...
-
# end
-
# end
-
#
-
1
def self.current_example
-
RSpec::Support.thread_local_data[:current_example]
-
end
-
-
# Set the current example being executed.
-
# @api private
-
1
def self.current_example=(example)
-
18
RSpec::Support.thread_local_data[:current_example] = example
-
end
-
-
# @private
-
# Internal container for global non-configuration data.
-
1
def self.world
-
71
@world ||= RSpec::Core::World.new
-
end
-
-
# Namespace for the rspec-core code.
-
1
module Core
-
1
autoload :ExampleStatusPersister, "rspec/core/example_status_persister"
-
1
autoload :Profiler, "rspec/core/profiler"
-
-
# @private
-
# This avoids issues with reporting time caused by examples that
-
# change the value/meaning of Time.now without properly restoring
-
# it.
-
1
class Time
-
1
class << self
-
1
define_method(:now, &::Time.method(:now))
-
end
-
end
-
-
# @private path to executable file.
-
1
def self.path_to_executable
-
@path_to_executable ||= File.expand_path('../../../exe/rspec', __FILE__)
-
end
-
end
-
-
# @private
-
1
MODULES_TO_AUTOLOAD = {
-
:Matchers => "rspec/expectations",
-
:Expectations => "rspec/expectations",
-
:Mocks => "rspec/mocks"
-
}
-
-
# @private
-
1
def self.const_missing(name)
-
# Load rspec-expectations when RSpec::Matchers is referenced. This allows
-
# people to define custom matchers (using `RSpec::Matchers.define`) before
-
# rspec-core has loaded rspec-expectations (since it delays the loading of
-
# it to allow users to configure a different assertion/expectation
-
# framework). `autoload` can't be used since it works with ruby's built-in
-
# require (e.g. for files that are available relative to a load path dir),
-
# but not with rubygems' extended require.
-
#
-
# As of rspec 2.14.1, we no longer require `rspec/mocks` and
-
# `rspec/expectations` when `rspec` is required, so we want
-
# to make them available as an autoload.
-
require MODULES_TO_AUTOLOAD.fetch(name) { return super }
-
::RSpec.const_get(name)
-
end
-
end
-
1
module RSpec
-
1
module Core
-
# @private
-
1
class BacktraceFormatter
-
# @private
-
1
attr_accessor :exclusion_patterns, :inclusion_patterns
-
-
1
def initialize
-
1
@full_backtrace = false
-
-
1
patterns = %w[ /lib\d*/ruby/ bin/ exe/rspec ]
-
1
patterns << "org/jruby/" if RUBY_PLATFORM == 'java'
-
4
patterns.map! { |s| Regexp.new(s.gsub("/", File::SEPARATOR)) }
-
-
1
@exclusion_patterns = [Regexp.union(RSpec::CallerFilter::IGNORE_REGEX, *patterns)]
-
1
@inclusion_patterns = []
-
-
1
return unless matches?(@exclusion_patterns, File.join(Dir.getwd, "lib", "foo.rb:13"))
-
inclusion_patterns << Regexp.new(Dir.getwd)
-
end
-
-
1
attr_writer :full_backtrace
-
-
1
def full_backtrace?
-
@full_backtrace || exclusion_patterns.empty?
-
end
-
-
1
def filter_gem(gem_name)
-
sep = File::SEPARATOR
-
exclusion_patterns << /#{sep}#{gem_name}(-[^#{sep}]+)?#{sep}/
-
end
-
-
1
def format_backtrace(backtrace, options={})
-
return [] unless backtrace
-
return backtrace if options[:full_backtrace] || backtrace.empty?
-
-
backtrace.map { |l| backtrace_line(l) }.compact.
-
tap do |filtered|
-
if filtered.empty?
-
filtered.concat backtrace
-
filtered << ""
-
filtered << " Showing full backtrace because every line was filtered out."
-
filtered << " See docs for RSpec::Configuration#backtrace_exclusion_patterns and"
-
filtered << " RSpec::Configuration#backtrace_inclusion_patterns for more information."
-
end
-
end
-
end
-
-
1
def backtrace_line(line)
-
Metadata.relative_path(line) unless exclude?(line)
-
end
-
-
1
def exclude?(line)
-
return false if @full_backtrace
-
matches?(exclusion_patterns, line) && !matches?(inclusion_patterns, line)
-
end
-
-
1
private
-
-
1
def matches?(patterns, line)
-
2
patterns.any? { |p| line =~ p }
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_core "backtrace_formatter"
-
1
RSpec::Support.require_rspec_core "ruby_project"
-
1
RSpec::Support.require_rspec_core "formatters/deprecation_formatter"
-
-
1
module RSpec
-
1
module Core
-
# rubocop:disable Metrics/ClassLength
-
-
# Stores runtime configuration information.
-
#
-
# Configuration options are loaded from `~/.rspec`, `.rspec`,
-
# `.rspec-local`, command line switches, and the `SPEC_OPTS` environment
-
# variable (listed in lowest to highest precedence; for example, an option
-
# in `~/.rspec` can be overridden by an option in `.rspec-local`).
-
#
-
# @example Standard settings
-
# RSpec.configure do |c|
-
# c.drb = true
-
# c.drb_port = 1234
-
# c.default_path = 'behavior'
-
# end
-
#
-
# @example Hooks
-
# RSpec.configure do |c|
-
# c.before(:suite) { establish_connection }
-
# c.before(:example) { log_in_as :authorized }
-
# c.around(:example) { |ex| Database.transaction(&ex) }
-
# end
-
#
-
# @see RSpec.configure
-
# @see Hooks
-
1
class Configuration
-
1
include RSpec::Core::Hooks
-
-
# Module that holds `attr_reader` declarations. It's in a separate
-
# module to allow us to override those methods and use `super`.
-
# @private
-
1
Readers = Module.new
-
1
include Readers
-
-
# @private
-
1
class MustBeConfiguredBeforeExampleGroupsError < StandardError; end
-
-
# @private
-
1
def self.define_reader(name)
-
29
Readers.class_eval do
-
29
remove_method name if method_defined?(name)
-
29
attr_reader name
-
end
-
-
359
define_method(name) { value_for(name) { super() } }
-
end
-
-
# @private
-
1
def self.define_aliases(name, alias_name)
-
alias_method alias_name, name
-
alias_method "#{alias_name}=", "#{name}="
-
define_predicate_for alias_name
-
end
-
-
# @private
-
1
def self.define_predicate_for(*names)
-
42
names.each { |name| alias_method "#{name}?", name }
-
end
-
-
# @private
-
#
-
# Invoked by the `add_setting` instance method. Use that method on a
-
# `Configuration` instance rather than this class method.
-
1
def self.add_setting(name, opts={})
-
20
raise "Use the instance add_setting method if you want to set a default" if opts.key?(:default)
-
20
attr_writer name
-
20
add_read_only_setting name
-
-
20
Array(opts[:alias_with]).each do |alias_name|
-
define_aliases(name, alias_name)
-
end
-
end
-
-
# @private
-
#
-
# As `add_setting` but only add the reader.
-
1
def self.add_read_only_setting(name, opts={})
-
21
raise "Use the instance add_setting method if you want to set a default" if opts.key?(:default)
-
21
define_reader name
-
21
define_predicate_for name
-
end
-
-
# @macro [attach] add_setting
-
# @!attribute [rw] $1
-
# @!method $1=(value)
-
#
-
# @macro [attach] define_reader
-
# @!attribute [r] $1
-
-
# @macro add_setting
-
# Path to use if no path is provided to the `rspec` command (default:
-
# `"spec"`). Allows you to just type `rspec` instead of `rspec spec` to
-
# run all the examples in the `spec` directory.
-
#
-
# @note Other scripts invoking `rspec` indirectly will ignore this
-
# setting.
-
1
add_read_only_setting :default_path
-
1
def default_path=(path)
-
project_source_dirs << path
-
@default_path = path
-
end
-
-
# @macro add_setting
-
# Run examples over DRb (default: `false`). RSpec doesn't supply the DRb
-
# server, but you can use tools like spork.
-
1
add_setting :drb
-
-
# @macro add_setting
-
# The drb_port (default: nil).
-
1
add_setting :drb_port
-
-
# @macro add_setting
-
# Default: `$stderr`.
-
1
add_setting :error_stream
-
-
# Indicates if the DSL has been exposed off of modules and `main`.
-
# Default: true
-
1
def expose_dsl_globally?
-
Core::DSL.exposed_globally?
-
end
-
-
# Use this to expose the core RSpec DSL via `Module` and the `main`
-
# object. It will be set automatically but you can override it to
-
# remove the DSL.
-
# Default: true
-
1
def expose_dsl_globally=(value)
-
1
if value
-
1
Core::DSL.expose_globally!
-
1
Core::SharedExampleGroup::TopLevelDSL.expose_globally!
-
else
-
Core::DSL.remove_globally!
-
Core::SharedExampleGroup::TopLevelDSL.remove_globally!
-
end
-
end
-
-
# Determines where deprecation warnings are printed.
-
# Defaults to `$stderr`.
-
# @return [IO, String] IO to write to or filename to write to
-
1
define_reader :deprecation_stream
-
-
# Determines where deprecation warnings are printed.
-
# @param value [IO, String] IO to write to or filename to write to
-
1
def deprecation_stream=(value)
-
if @reporter && !value.equal?(@deprecation_stream)
-
warn "RSpec's reporter has already been initialized with " \
-
"#{deprecation_stream.inspect} as the deprecation stream, so your change to "\
-
"`deprecation_stream` will be ignored. You should configure it earlier for " \
-
"it to take effect, or use the `--deprecation-out` CLI option. " \
-
"(Called from #{CallerFilter.first_non_rspec_line})"
-
else
-
@deprecation_stream = value
-
end
-
end
-
-
# @macro define_reader
-
# The file path to use for persisting example statuses. Necessary for the
-
# `--only-failures` and `--next-failures` CLI options.
-
#
-
# @overload example_status_persistence_file_path
-
# @return [String] the file path
-
# @overload example_status_persistence_file_path=(value)
-
# @param value [String] the file path
-
1
define_reader :example_status_persistence_file_path
-
-
# Sets the file path to use for persisting example statuses. Necessary for the
-
# `--only-failures` and `--next-failures` CLI options.
-
1
def example_status_persistence_file_path=(value)
-
@example_status_persistence_file_path = value
-
clear_values_derived_from_example_status_persistence_file_path
-
end
-
-
# @macro define_reader
-
# Indicates if the `--only-failures` (or `--next-failure`) flag is being used.
-
1
define_reader :only_failures
-
1
alias_method :only_failures?, :only_failures
-
-
# @private
-
1
def only_failures_but_not_configured?
-
1
only_failures? && !example_status_persistence_file_path
-
end
-
-
# @macro add_setting
-
# If specified, indicates the number of failures required before cleaning
-
# up and exit (default: `nil`).
-
1
add_setting :fail_fast
-
-
# @macro add_setting
-
# Prints the formatter output of your suite without running any
-
# examples or hooks.
-
1
add_setting :dry_run
-
-
# @macro add_setting
-
# The exit code to return if there are any failures (default: 1).
-
1
add_setting :failure_exit_code
-
-
# @macro define_reader
-
# Indicates files configured to be required.
-
1
define_reader :requires
-
-
# @macro define_reader
-
# Returns dirs that have been prepended to the load path by the `-I`
-
# command line option.
-
1
define_reader :libs
-
-
# @macro add_setting
-
# Determines where RSpec will send its output.
-
# Default: `$stdout`.
-
1
define_reader :output_stream
-
-
# Set the output stream for reporter.
-
# @attr value [IO] value for output, defaults to $stdout
-
1
def output_stream=(value)
-
1
if @reporter && !value.equal?(@output_stream)
-
warn "RSpec's reporter has already been initialized with " \
-
"#{output_stream.inspect} as the output stream, so your change to "\
-
"`output_stream` will be ignored. You should configure it earlier for " \
-
"it to take effect. (Called from #{CallerFilter.first_non_rspec_line})"
-
else
-
1
@output_stream = value
-
end
-
end
-
-
# @macro define_reader
-
# Load files matching this pattern (default: `'**{,/*/**}/*_spec.rb'`).
-
1
define_reader :pattern
-
-
# Set pattern to match files to load.
-
# @attr value [String] the filename pattern to filter spec files by
-
1
def pattern=(value)
-
update_pattern_attr :pattern, value
-
end
-
-
# @macro define_reader
-
# Exclude files matching this pattern.
-
1
define_reader :exclude_pattern
-
-
# Set pattern to match files to exclude.
-
# @attr value [String] the filename pattern to exclude spec files by
-
1
def exclude_pattern=(value)
-
update_pattern_attr :exclude_pattern, value
-
end
-
-
# @macro add_setting
-
# Specifies which directories contain the source code for your project.
-
# When a failure occurs, RSpec looks through the backtrace to find a
-
# a line of source to print. It first looks for a line coming from
-
# one of the project source directories so that, for example, it prints
-
# the expectation or assertion call rather than the source code from
-
# the expectation or assertion framework.
-
# @return [Array<String>]
-
1
add_setting :project_source_dirs
-
-
# @macro add_setting
-
# Report the times for the slowest examples (default: `false`).
-
# Use this to specify the number of examples to include in the profile.
-
1
add_setting :profile_examples
-
-
# @macro add_setting
-
# Run all examples if none match the configured filters
-
# (default: `false`).
-
1
add_setting :run_all_when_everything_filtered
-
-
# @macro add_setting
-
# Color to use to indicate success.
-
# @param color [Symbol] defaults to `:green` but can be set to one of the
-
# following: `[:black, :white, :red, :green, :yellow, :blue, :magenta,
-
# :cyan]`
-
1
add_setting :success_color
-
-
# @macro add_setting
-
# Color to use to print pending examples.
-
# @param color [Symbol] defaults to `:yellow` but can be set to one of the
-
# following: `[:black, :white, :red, :green, :yellow, :blue, :magenta,
-
# :cyan]`
-
1
add_setting :pending_color
-
-
# @macro add_setting
-
# Color to use to indicate failure.
-
# @param color [Symbol] defaults to `:red` but can be set to one of the
-
# following: `[:black, :white, :red, :green, :yellow, :blue, :magenta,
-
# :cyan]`
-
1
add_setting :failure_color
-
-
# @macro add_setting
-
# The default output color.
-
# @param color [Symbol] defaults to `:white` but can be set to one of the
-
# following: `[:black, :white, :red, :green, :yellow, :blue, :magenta,
-
# :cyan]`
-
1
add_setting :default_color
-
-
# @macro add_setting
-
# Color used when a pending example is fixed.
-
# @param color [Symbol] defaults to `:blue` but can be set to one of the
-
# following: `[:black, :white, :red, :green, :yellow, :blue, :magenta,
-
# :cyan]`
-
1
add_setting :fixed_color
-
-
# @macro add_setting
-
# Color used to print details.
-
# @param color [Symbol] defaults to `:cyan` but can be set to one of the
-
# following: `[:black, :white, :red, :green, :yellow, :blue, :magenta,
-
# :cyan]`
-
1
add_setting :detail_color
-
-
# @macro add_setting
-
# Don't print filter info i.e. "Run options: include {:focus=>true}"
-
# (default `false`).
-
1
add_setting :silence_filter_announcements
-
-
# Deprecated. This config option was added in RSpec 2 to pave the way
-
# for this being the default behavior in RSpec 3. Now this option is
-
# a no-op.
-
1
def treat_symbols_as_metadata_keys_with_true_values=(_value)
-
RSpec.deprecate(
-
"RSpec::Core::Configuration#treat_symbols_as_metadata_keys_with_true_values=",
-
:message => "RSpec::Core::Configuration#treat_symbols_as_metadata_keys_with_true_values= " \
-
"is deprecated, it is now set to true as default and " \
-
"setting it to false has no effect."
-
)
-
end
-
-
# Record the start time of the spec suite to measure load time.
-
1
add_setting :start_time
-
-
# @macro add_setting
-
# Use threadsafe options where available.
-
# Currently this will place a mutex around memoized values such as let blocks.
-
1
add_setting :threadsafe
-
-
# @macro add_setting
-
# Maximum count of failed source lines to display in the failure reports.
-
# (default `10`).
-
1
add_setting :max_displayed_failure_line_count
-
-
# @private
-
1
add_setting :tty
-
# @private
-
1
attr_writer :files_to_run
-
# @private
-
1
attr_accessor :filter_manager
-
# @private
-
1
attr_accessor :static_config_filter_manager
-
# @private
-
1
attr_reader :backtrace_formatter, :ordering_manager, :loaded_spec_files
-
-
1
def initialize
-
# rubocop:disable Style/GlobalVars
-
1
@start_time = $_rspec_core_load_started_at || ::RSpec::Core::Time.now
-
# rubocop:enable Style/GlobalVars
-
1
@expectation_frameworks = []
-
1
@include_modules = FilterableItemRepository::QueryOptimized.new(:any?)
-
1
@extend_modules = FilterableItemRepository::QueryOptimized.new(:any?)
-
1
@prepend_modules = FilterableItemRepository::QueryOptimized.new(:any?)
-
-
1
@before_suite_hooks = []
-
1
@after_suite_hooks = []
-
-
1
@mock_framework = nil
-
1
@files_or_directories_to_run = []
-
1
@loaded_spec_files = Set.new
-
1
@color = false
-
1
@pattern = '**{,/*/**}/*_spec.rb'
-
1
@exclude_pattern = ''
-
1
@failure_exit_code = 1
-
1
@spec_files_loaded = false
-
-
1
@backtrace_formatter = BacktraceFormatter.new
-
-
1
@default_path = 'spec'
-
1
@project_source_dirs = %w[ spec lib app ]
-
1
@deprecation_stream = $stderr
-
1
@output_stream = $stdout
-
1
@reporter = nil
-
1
@reporter_buffer = nil
-
1
@filter_manager = FilterManager.new
-
1
@static_config_filter_manager = FilterManager.new
-
1
@ordering_manager = Ordering::ConfigurationManager.new
-
1
@preferred_options = {}
-
1
@failure_color = :red
-
1
@success_color = :green
-
1
@pending_color = :yellow
-
1
@default_color = :white
-
1
@fixed_color = :blue
-
1
@detail_color = :cyan
-
1
@profile_examples = false
-
1
@requires = []
-
1
@libs = []
-
1
@derived_metadata_blocks = FilterableItemRepository::QueryOptimized.new(:any?)
-
1
@threadsafe = true
-
1
@max_displayed_failure_line_count = 10
-
-
1
define_built_in_hooks
-
end
-
-
# @private
-
#
-
# Used to set higher priority option values from the command line.
-
1
def force(hash)
-
1
ordering_manager.force(hash)
-
1
@preferred_options.merge!(hash)
-
-
1
return unless hash.key?(:example_status_persistence_file_path)
-
clear_values_derived_from_example_status_persistence_file_path
-
end
-
-
# @private
-
1
def reset
-
@spec_files_loaded = false
-
@reporter = nil
-
@formatter_loader = nil
-
end
-
-
# @private
-
1
def reset_filters
-
self.filter_manager = FilterManager.new
-
filter_manager.include_only(
-
Metadata.deep_hash_dup(static_config_filter_manager.inclusions.rules)
-
)
-
filter_manager.exclude_only(
-
Metadata.deep_hash_dup(static_config_filter_manager.exclusions.rules)
-
)
-
end
-
-
# @overload add_setting(name)
-
# @overload add_setting(name, opts)
-
# @option opts [Symbol] :default
-
#
-
# Set a default value for the generated getter and predicate methods:
-
#
-
# add_setting(:foo, :default => "default value")
-
#
-
# @option opts [Symbol] :alias_with
-
#
-
# Use `:alias_with` to alias the setter, getter, and predicate to
-
# another name, or names:
-
#
-
# add_setting(:foo, :alias_with => :bar)
-
# add_setting(:foo, :alias_with => [:bar, :baz])
-
#
-
# Adds a custom setting to the RSpec.configuration object.
-
#
-
# RSpec.configuration.add_setting :foo
-
#
-
# Used internally and by extension frameworks like rspec-rails, so they
-
# can add config settings that are domain specific. For example:
-
#
-
# RSpec.configure do |c|
-
# c.add_setting :use_transactional_fixtures,
-
# :default => true,
-
# :alias_with => :use_transactional_examples
-
# end
-
#
-
# `add_setting` creates three methods on the configuration object, a
-
# setter, a getter, and a predicate:
-
#
-
# RSpec.configuration.foo=(value)
-
# RSpec.configuration.foo
-
# RSpec.configuration.foo? # Returns true if foo returns anything but nil or false.
-
1
def add_setting(name, opts={})
-
default = opts.delete(:default)
-
(class << self; self; end).class_exec do
-
add_setting(name, opts)
-
end
-
__send__("#{name}=", default) if default
-
end
-
-
# Returns the configured mock framework adapter module.
-
1
def mock_framework
-
1
if @mock_framework.nil?
-
1
begin
-
1
mock_with :rspec
-
rescue LoadError
-
mock_with :nothing
-
end
-
end
-
1
@mock_framework
-
end
-
-
# Delegates to mock_framework=(framework).
-
1
def mock_framework=(framework)
-
mock_with framework
-
end
-
-
# Regexps used to exclude lines from backtraces.
-
#
-
# Excludes lines from ruby (and jruby) source, installed gems, anything
-
# in any "bin" directory, and any of the RSpec libs (outside gem
-
# installs) by default.
-
#
-
# You can modify the list via the getter, or replace it with the setter.
-
#
-
# To override this behaviour and display a full backtrace, use
-
# `--backtrace` on the command line, in a `.rspec` file, or in the
-
# `rspec_options` attribute of RSpec's rake task.
-
1
def backtrace_exclusion_patterns
-
@backtrace_formatter.exclusion_patterns
-
end
-
-
# Set regular expressions used to exclude lines in backtrace.
-
# @param patterns [Regexp] set the backtrace exlusion pattern
-
1
def backtrace_exclusion_patterns=(patterns)
-
@backtrace_formatter.exclusion_patterns = patterns
-
end
-
-
# Regexps used to include lines in backtraces.
-
#
-
# Defaults to [Regexp.new Dir.getwd].
-
#
-
# Lines that match an exclusion _and_ an inclusion pattern
-
# will be included.
-
#
-
# You can modify the list via the getter, or replace it with the setter.
-
1
def backtrace_inclusion_patterns
-
@backtrace_formatter.inclusion_patterns
-
end
-
-
# Set regular expressions used to include lines in backtrace.
-
# @attr patterns [Regexp] set backtrace_formatter inclusion_patterns
-
1
def backtrace_inclusion_patterns=(patterns)
-
@backtrace_formatter.inclusion_patterns = patterns
-
end
-
-
# Adds {#backtrace_exclusion_patterns} that will filter lines from
-
# the named gems from backtraces.
-
#
-
# @param gem_names [Array<String>] Names of the gems to filter
-
#
-
# @example
-
# RSpec.configure do |config|
-
# config.filter_gems_from_backtrace "rack", "rake"
-
# end
-
#
-
# @note The patterns this adds will match the named gems in their common
-
# locations (e.g. system gems, vendored with bundler, installed as a
-
# :git dependency with bundler, etc) but is not guaranteed to work for
-
# all possible gem locations. For example, if you have the gem source
-
# in a directory with a completely unrelated name, and use bundler's
-
# :path option, this will not filter it.
-
1
def filter_gems_from_backtrace(*gem_names)
-
gem_names.each do |name|
-
@backtrace_formatter.filter_gem(name)
-
end
-
end
-
-
# @private
-
1
MOCKING_ADAPTERS = {
-
:rspec => :RSpec,
-
:flexmock => :Flexmock,
-
:rr => :RR,
-
:mocha => :Mocha,
-
:nothing => :Null
-
}
-
-
# Sets the mock framework adapter module.
-
#
-
# `framework` can be a Symbol or a Module.
-
#
-
# Given any of `:rspec`, `:mocha`, `:flexmock`, or `:rr`, configures the
-
# named framework.
-
#
-
# Given `:nothing`, configures no framework. Use this if you don't use
-
# any mocking framework to save a little bit of overhead.
-
#
-
# Given a Module, includes that module in every example group. The module
-
# should adhere to RSpec's mock framework adapter API:
-
#
-
# setup_mocks_for_rspec
-
# - called before each example
-
#
-
# verify_mocks_for_rspec
-
# - called after each example if the example hasn't yet failed.
-
# Framework should raise an exception when expectations fail
-
#
-
# teardown_mocks_for_rspec
-
# - called after verify_mocks_for_rspec (even if there are errors)
-
#
-
# If the module responds to `configuration` and `mock_with` receives a
-
# block, it will yield the configuration object to the block e.g.
-
#
-
# config.mock_with OtherMockFrameworkAdapter do |mod_config|
-
# mod_config.custom_setting = true
-
# end
-
1
def mock_with(framework)
-
1
framework_module =
-
if framework.is_a?(Module)
-
framework
-
else
-
1
const_name = MOCKING_ADAPTERS.fetch(framework) do
-
raise ArgumentError,
-
"Unknown mocking framework: #{framework.inspect}. " \
-
"Pass a module or one of #{MOCKING_ADAPTERS.keys.inspect}"
-
end
-
-
1
RSpec::Support.require_rspec_core "mocking_adapters/#{const_name.to_s.downcase}"
-
1
RSpec::Core::MockingAdapters.const_get(const_name)
-
end
-
-
1
new_name, old_name = [framework_module, @mock_framework].map do |mod|
-
2
mod.respond_to?(:framework_name) ? mod.framework_name : :unnamed
-
end
-
-
1
unless new_name == old_name
-
1
assert_no_example_groups_defined(:mock_framework)
-
end
-
-
1
if block_given?
-
raise "#{framework_module} must respond to `configuration` so that " \
-
"mock_with can yield it." unless framework_module.respond_to?(:configuration)
-
yield framework_module.configuration
-
end
-
-
1
@mock_framework = framework_module
-
end
-
-
# Returns the configured expectation framework adapter module(s)
-
1
def expectation_frameworks
-
1
if @expectation_frameworks.empty?
-
1
begin
-
1
expect_with :rspec
-
rescue LoadError
-
expect_with Module.new
-
end
-
end
-
1
@expectation_frameworks
-
end
-
-
# Delegates to expect_with(framework).
-
1
def expectation_framework=(framework)
-
expect_with(framework)
-
end
-
-
# Sets the expectation framework module(s) to be included in each example
-
# group.
-
#
-
# `frameworks` can be `:rspec`, `:test_unit`, `:minitest`, a custom
-
# module, or any combination thereof:
-
#
-
# config.expect_with :rspec
-
# config.expect_with :test_unit
-
# config.expect_with :minitest
-
# config.expect_with :rspec, :minitest
-
# config.expect_with OtherExpectationFramework
-
#
-
# RSpec will translate `:rspec`, `:minitest`, and `:test_unit` into the
-
# appropriate modules.
-
#
-
# ## Configuration
-
#
-
# If the module responds to `configuration`, `expect_with` will
-
# yield the `configuration` object if given a block:
-
#
-
# config.expect_with OtherExpectationFramework do |custom_config|
-
# custom_config.custom_setting = true
-
# end
-
1
def expect_with(*frameworks)
-
1
modules = frameworks.map do |framework|
-
1
case framework
-
when Module
-
framework
-
when :rspec
-
1
require 'rspec/expectations'
-
-
# Tag this exception class so our exception formatting logic knows
-
# that it satisfies the `MultipleExceptionError` interface.
-
1
::RSpec::Expectations::MultipleExpectationsNotMetError.__send__(
-
:include, MultipleExceptionError::InterfaceTag
-
)
-
-
1
::RSpec::Matchers
-
when :test_unit
-
require 'rspec/core/test_unit_assertions_adapter'
-
::RSpec::Core::TestUnitAssertionsAdapter
-
when :minitest
-
require 'rspec/core/minitest_assertions_adapter'
-
::RSpec::Core::MinitestAssertionsAdapter
-
else
-
raise ArgumentError, "#{framework.inspect} is not supported"
-
end
-
end
-
-
1
if (modules - @expectation_frameworks).any?
-
1
assert_no_example_groups_defined(:expect_with)
-
end
-
-
1
if block_given?
-
raise "expect_with only accepts a block with a single argument. " \
-
"Call expect_with #{modules.length} times, " \
-
"once with each argument, instead." if modules.length > 1
-
raise "#{modules.first} must respond to `configuration` so that " \
-
"expect_with can yield it." unless modules.first.respond_to?(:configuration)
-
yield modules.first.configuration
-
end
-
-
1
@expectation_frameworks.push(*modules)
-
end
-
-
# Check if full backtrace is enabled.
-
# @return [Boolean] is full backtrace enabled
-
1
def full_backtrace?
-
@backtrace_formatter.full_backtrace?
-
end
-
-
# Toggle full backtrace.
-
# @attr true_or_false [Boolean] toggle full backtrace display
-
1
def full_backtrace=(true_or_false)
-
@backtrace_formatter.full_backtrace = true_or_false
-
end
-
-
# Returns the configuration option for color, but should not
-
# be used to check if color is supported.
-
#
-
# @see color_enabled?
-
# @return [Boolean]
-
1
def color
-
value_for(:color) { @color }
-
end
-
-
# Check if color is enabled for a particular output.
-
# @param output [IO] an output stream to use, defaults to the current
-
# `output_stream`
-
# @return [Boolean]
-
1
def color_enabled?(output=output_stream)
-
output_to_tty?(output) && color
-
end
-
-
# Toggle output color.
-
1
attr_writer :color
-
-
# @private
-
1
def libs=(libs)
-
1
libs.map do |lib|
-
@libs.unshift lib
-
$LOAD_PATH.unshift lib
-
end
-
end
-
-
# Run examples matching on `description` in all files to run.
-
# @param description [String, Regexp] the pattern to filter on
-
1
def full_description=(description)
-
filter_run :full_description => Regexp.union(*Array(description).map { |d| Regexp.new(d) })
-
end
-
-
# @return [Array] full description filter
-
1
def full_description
-
filter.fetch :full_description, nil
-
end
-
-
# @overload add_formatter(formatter)
-
#
-
# Adds a formatter to the formatters collection. `formatter` can be a
-
# string representing any of the built-in formatters (see
-
# `built_in_formatter`), or a custom formatter class.
-
#
-
# ### Note
-
#
-
# For internal purposes, `add_formatter` also accepts the name of a class
-
# and paths to use for output streams, but you should consider that a
-
# private api that may change at any time without notice.
-
1
def add_formatter(formatter_to_use, *paths)
-
1
paths << output_stream if paths.empty?
-
1
formatter_loader.add formatter_to_use, *paths
-
end
-
1
alias_method :formatter=, :add_formatter
-
-
# The formatter that will be used if no formatter has been set.
-
# Defaults to 'progress'.
-
1
def default_formatter
-
formatter_loader.default_formatter
-
end
-
-
# Sets a fallback formatter to use if none other has been set.
-
#
-
# @example
-
#
-
# RSpec.configure do |rspec|
-
# rspec.default_formatter = 'doc'
-
# end
-
1
def default_formatter=(value)
-
formatter_loader.default_formatter = value
-
end
-
-
# Returns a duplicate of the formatters currently loaded in
-
# the `FormatterLoader` for introspection.
-
#
-
# Note as this is a duplicate, any mutations will be disregarded.
-
#
-
# @return [Array] the formatters currently loaded
-
1
def formatters
-
formatter_loader.formatters.dup
-
end
-
-
# @private
-
1
def formatter_loader
-
4
@formatter_loader ||= Formatters::Loader.new(Reporter.new(self))
-
end
-
-
# @private
-
#
-
# This buffer is used to capture all messages sent to the reporter during
-
# reporter initialization. It can then replay those messages after the
-
# formatter is correctly initialized. Otherwise, deprecation warnings
-
# during formatter initialization can cause an infinite loop.
-
1
class DeprecationReporterBuffer
-
1
def initialize
-
1
@calls = []
-
end
-
-
1
def deprecation(*args)
-
@calls << args
-
end
-
-
1
def play_onto(reporter)
-
1
@calls.each do |args|
-
reporter.deprecation(*args)
-
end
-
end
-
end
-
-
# @private
-
1
def reporter
-
# @reporter_buffer should only ever be set in this method to cover
-
# initialization of @reporter.
-
@reporter_buffer || @reporter ||=
-
begin
-
1
@reporter_buffer = DeprecationReporterBuffer.new
-
1
formatter_loader.setup_default output_stream, deprecation_stream
-
1
@reporter_buffer.play_onto(formatter_loader.reporter)
-
1
@reporter_buffer = nil
-
1
formatter_loader.reporter
-
1
end
-
end
-
-
# @api private
-
#
-
# Defaults `profile_examples` to 10 examples when `@profile_examples` is
-
# `true`.
-
1
def profile_examples
-
profile = value_for(:profile_examples) { @profile_examples }
-
if profile && !profile.is_a?(Integer)
-
10
-
else
-
profile
-
end
-
end
-
-
# @private
-
1
def files_or_directories_to_run=(*files)
-
1
files = files.flatten
-
-
1
if (command == 'rspec' || Runner.running_in_drb?) && default_path && files.empty?
-
files << default_path
-
end
-
-
1
@files_or_directories_to_run = files
-
1
@files_to_run = nil
-
end
-
-
# The spec files RSpec will run.
-
# @return [Array] specified files about to run
-
1
def files_to_run
-
1
@files_to_run ||= get_files_to_run(@files_or_directories_to_run)
-
end
-
-
# @private
-
1
def last_run_statuses
-
@last_run_statuses ||= Hash.new(UNKNOWN_STATUS).tap do |statuses|
-
1
if (path = example_status_persistence_file_path)
-
begin
-
ExampleStatusPersister.load_from(path).inject(statuses) do |hash, example|
-
hash[example.fetch(:example_id)] = example.fetch(:status)
-
hash
-
end
-
rescue SystemCallError => e
-
RSpec.warning "Could not read from #{path.inspect} (configured as " \
-
"`config.example_status_persistence_file_path`) due " \
-
"to a system error: #{e.inspect}. Please check that " \
-
"the config option is set to an accessible, valid " \
-
"file path", :call_site => nil
-
end
-
end
-
10
end
-
end
-
-
# @private
-
1
UNKNOWN_STATUS = "unknown".freeze
-
-
# @private
-
1
FAILED_STATUS = "failed".freeze
-
-
# @private
-
1
def spec_files_with_failures
-
@spec_files_with_failures ||= last_run_statuses.inject(Set.new) do |files, (id, status)|
-
files << Example.parse_id(id).first if status == FAILED_STATUS
-
files
-
end.to_a
-
end
-
-
# Creates a method that delegates to `example` including the submitted
-
# `args`. Used internally to add variants of `example` like `pending`:
-
# @param name [String] example name alias
-
# @param args [Array<Symbol>, Hash] metadata for the generated example
-
#
-
# @note The specific example alias below (`pending`) is already
-
# defined for you.
-
# @note Use with caution. This extends the language used in your
-
# specs, but does not add any additional documentation. We use this
-
# in RSpec to define methods like `focus` and `xit`, but we also add
-
# docs for those methods.
-
#
-
# @example
-
# RSpec.configure do |config|
-
# config.alias_example_to :pending, :pending => true
-
# end
-
#
-
# # This lets you do this:
-
#
-
# describe Thing do
-
# pending "does something" do
-
# thing = Thing.new
-
# end
-
# end
-
#
-
# # ... which is the equivalent of
-
#
-
# describe Thing do
-
# it "does something", :pending => true do
-
# thing = Thing.new
-
# end
-
# end
-
1
def alias_example_to(name, *args)
-
extra_options = Metadata.build_hash_from(args)
-
RSpec::Core::ExampleGroup.define_example_method(name, extra_options)
-
end
-
-
# Creates a method that defines an example group with the provided
-
# metadata. Can be used to define example group/metadata shortcuts.
-
#
-
# @example
-
# RSpec.configure do |config|
-
# config.alias_example_group_to :describe_model, :type => :model
-
# end
-
#
-
# shared_context_for "model tests", :type => :model do
-
# # define common model test helper methods, `let` declarations, etc
-
# end
-
#
-
# # This lets you do this:
-
#
-
# RSpec.describe_model User do
-
# end
-
#
-
# # ... which is the equivalent of
-
#
-
# RSpec.describe User, :type => :model do
-
# end
-
#
-
# @note The defined aliased will also be added to the top level
-
# (e.g. `main` and from within modules) if
-
# `expose_dsl_globally` is set to true.
-
# @see #alias_example_to
-
# @see #expose_dsl_globally=
-
1
def alias_example_group_to(new_name, *args)
-
extra_options = Metadata.build_hash_from(args)
-
RSpec::Core::ExampleGroup.define_example_group_method(new_name, extra_options)
-
end
-
-
# Define an alias for it_should_behave_like that allows different
-
# language (like "it_has_behavior" or "it_behaves_like") to be
-
# employed when including shared examples.
-
#
-
# @example
-
# RSpec.configure do |config|
-
# config.alias_it_behaves_like_to(:it_has_behavior, 'has behavior:')
-
# end
-
#
-
# # allows the user to include a shared example group like:
-
#
-
# describe Entity do
-
# it_has_behavior 'sortability' do
-
# let(:sortable) { Entity.new }
-
# end
-
# end
-
#
-
# # which is reported in the output as:
-
# # Entity
-
# # has behavior: sortability
-
# # ...sortability examples here
-
#
-
# @note Use with caution. This extends the language used in your
-
# specs, but does not add any additional documentation. We use this
-
# in RSpec to define `it_should_behave_like` (for backward
-
# compatibility), but we also add docs for that method.
-
1
def alias_it_behaves_like_to(new_name, report_label='')
-
RSpec::Core::ExampleGroup.define_nested_shared_group_method(new_name, report_label)
-
end
-
1
alias_method :alias_it_should_behave_like_to, :alias_it_behaves_like_to
-
-
# Adds key/value pairs to the `inclusion_filter`. If `args`
-
# includes any symbols that are not part of the hash, each symbol
-
# is treated as a key in the hash with the value `true`.
-
#
-
# ### Note
-
#
-
# Filters set using this method can be overridden from the command line
-
# or config files (e.g. `.rspec`).
-
#
-
# @example
-
# # Given this declaration.
-
# describe "something", :foo => 'bar' do
-
# # ...
-
# end
-
#
-
# # Any of the following will include that group.
-
# config.filter_run_including :foo => 'bar'
-
# config.filter_run_including :foo => /^ba/
-
# config.filter_run_including :foo => lambda {|v| v == 'bar'}
-
# config.filter_run_including :foo => lambda {|v,m| m[:foo] == 'bar'}
-
#
-
# # Given a proc with an arity of 1, the lambda is passed the value
-
# # related to the key, e.g.
-
# config.filter_run_including :foo => lambda {|v| v == 'bar'}
-
#
-
# # Given a proc with an arity of 2, the lambda is passed the value
-
# # related to the key, and the metadata itself e.g.
-
# config.filter_run_including :foo => lambda {|v,m| m[:foo] == 'bar'}
-
#
-
# filter_run_including :foo # same as filter_run_including :foo => true
-
1
def filter_run_including(*args)
-
meta = Metadata.build_hash_from(args, :warn_about_example_group_filtering)
-
filter_manager.include_with_low_priority meta
-
static_config_filter_manager.include_with_low_priority Metadata.deep_hash_dup(meta)
-
end
-
-
1
alias_method :filter_run, :filter_run_including
-
-
# Clears and reassigns the `inclusion_filter`. Set to `nil` if you don't
-
# want any inclusion filter at all.
-
#
-
# ### Warning
-
#
-
# This overrides any inclusion filters/tags set on the command line or in
-
# configuration files.
-
1
def inclusion_filter=(filter)
-
meta = Metadata.build_hash_from([filter], :warn_about_example_group_filtering)
-
filter_manager.include_only meta
-
end
-
-
1
alias_method :filter=, :inclusion_filter=
-
-
# Returns the `inclusion_filter`. If none has been set, returns an empty
-
# hash.
-
1
def inclusion_filter
-
1
filter_manager.inclusions
-
end
-
-
1
alias_method :filter, :inclusion_filter
-
-
# Adds key/value pairs to the `exclusion_filter`. If `args`
-
# includes any symbols that are not part of the hash, each symbol
-
# is treated as a key in the hash with the value `true`.
-
#
-
# ### Note
-
#
-
# Filters set using this method can be overridden from the command line
-
# or config files (e.g. `.rspec`).
-
#
-
# @example
-
# # Given this declaration.
-
# describe "something", :foo => 'bar' do
-
# # ...
-
# end
-
#
-
# # Any of the following will exclude that group.
-
# config.filter_run_excluding :foo => 'bar'
-
# config.filter_run_excluding :foo => /^ba/
-
# config.filter_run_excluding :foo => lambda {|v| v == 'bar'}
-
# config.filter_run_excluding :foo => lambda {|v,m| m[:foo] == 'bar'}
-
#
-
# # Given a proc with an arity of 1, the lambda is passed the value
-
# # related to the key, e.g.
-
# config.filter_run_excluding :foo => lambda {|v| v == 'bar'}
-
#
-
# # Given a proc with an arity of 2, the lambda is passed the value
-
# # related to the key, and the metadata itself e.g.
-
# config.filter_run_excluding :foo => lambda {|v,m| m[:foo] == 'bar'}
-
#
-
# filter_run_excluding :foo # same as filter_run_excluding :foo => true
-
1
def filter_run_excluding(*args)
-
meta = Metadata.build_hash_from(args, :warn_about_example_group_filtering)
-
filter_manager.exclude_with_low_priority meta
-
static_config_filter_manager.exclude_with_low_priority Metadata.deep_hash_dup(meta)
-
end
-
-
# Clears and reassigns the `exclusion_filter`. Set to `nil` if you don't
-
# want any exclusion filter at all.
-
#
-
# ### Warning
-
#
-
# This overrides any exclusion filters/tags set on the command line or in
-
# configuration files.
-
1
def exclusion_filter=(filter)
-
meta = Metadata.build_hash_from([filter], :warn_about_example_group_filtering)
-
filter_manager.exclude_only meta
-
end
-
-
# Returns the `exclusion_filter`. If none has been set, returns an empty
-
# hash.
-
1
def exclusion_filter
-
1
filter_manager.exclusions
-
end
-
-
# Tells RSpec to include `mod` in example groups. Methods defined in
-
# `mod` are exposed to examples (not example groups). Use `filters` to
-
# constrain the groups or examples in which to include the module.
-
#
-
# @example
-
#
-
# module AuthenticationHelpers
-
# def login_as(user)
-
# # ...
-
# end
-
# end
-
#
-
# module UserHelpers
-
# def users(username)
-
# # ...
-
# end
-
# end
-
#
-
# RSpec.configure do |config|
-
# config.include(UserHelpers) # included in all modules
-
# config.include(AuthenticationHelpers, :type => :request)
-
# end
-
#
-
# describe "edit profile", :type => :request do
-
# it "can be viewed by owning user" do
-
# login_as users(:jdoe)
-
# get "/profiles/jdoe"
-
# assert_select ".username", :text => 'jdoe'
-
# end
-
# end
-
#
-
# @note Filtered module inclusions can also be applied to
-
# individual examples that have matching metadata. Just like
-
# Ruby's object model is that every object has a singleton class
-
# which has only a single instance, RSpec's model is that every
-
# example has a singleton example group containing just the one
-
# example.
-
#
-
# @see #extend
-
# @see #prepend
-
1
def include(mod, *filters)
-
meta = Metadata.build_hash_from(filters, :warn_about_example_group_filtering)
-
@include_modules.append(mod, meta)
-
configure_existing_groups(mod, meta, :safe_include)
-
end
-
-
# Tells RSpec to extend example groups with `mod`. Methods defined in
-
# `mod` are exposed to example groups (not examples). Use `filters` to
-
# constrain the groups to extend.
-
#
-
# Similar to `include`, but behavior is added to example groups, which
-
# are classes, rather than the examples, which are instances of those
-
# classes.
-
#
-
# @example
-
#
-
# module UiHelpers
-
# def run_in_browser
-
# # ...
-
# end
-
# end
-
#
-
# RSpec.configure do |config|
-
# config.extend(UiHelpers, :type => :request)
-
# end
-
#
-
# describe "edit profile", :type => :request do
-
# run_in_browser
-
#
-
# it "does stuff in the client" do
-
# # ...
-
# end
-
# end
-
#
-
# @see #include
-
# @see #prepend
-
1
def extend(mod, *filters)
-
1
meta = Metadata.build_hash_from(filters, :warn_about_example_group_filtering)
-
1
@extend_modules.append(mod, meta)
-
1
configure_existing_groups(mod, meta, :safe_extend)
-
end
-
-
1
if RSpec::Support::RubyFeatures.module_prepends_supported?
-
# Tells RSpec to prepend example groups with `mod`. Methods defined in
-
# `mod` are exposed to examples (not example groups). Use `filters` to
-
# constrain the groups in which to prepend the module.
-
#
-
# Similar to `include`, but module is included before the example group's class
-
# in the ancestor chain.
-
#
-
# @example
-
#
-
# module OverrideMod
-
# def override_me
-
# "overridden"
-
# end
-
# end
-
#
-
# RSpec.configure do |config|
-
# config.prepend(OverrideMod, :method => :prepend)
-
# end
-
#
-
# describe "overriding example's class", :method => :prepend do
-
# it "finds the user" do
-
# self.class.class_eval do
-
# def override_me
-
# end
-
# end
-
# override_me # => "overridden"
-
# # ...
-
# end
-
# end
-
#
-
# @see #include
-
# @see #extend
-
1
def prepend(mod, *filters)
-
meta = Metadata.build_hash_from(filters, :warn_about_example_group_filtering)
-
@prepend_modules.append(mod, meta)
-
configure_existing_groups(mod, meta, :safe_prepend)
-
end
-
end
-
-
# @private
-
#
-
# Used internally to extend a group with modules using `include`, `prepend` and/or
-
# `extend`.
-
1
def configure_group(group)
-
11
configure_group_with group, @include_modules, :safe_include
-
11
configure_group_with group, @extend_modules, :safe_extend
-
11
configure_group_with group, @prepend_modules, :safe_prepend
-
end
-
-
# @private
-
1
def configure_group_with(group, module_list, application_method)
-
33
module_list.items_for(group.metadata).each do |mod|
-
11
__send__(application_method, mod, group)
-
end
-
end
-
-
# @private
-
1
def configure_existing_groups(mod, meta, application_method)
-
1
RSpec.world.all_example_groups.each do |group|
-
next unless meta.empty? || MetadataFilter.apply?(:any?, meta, group.metadata)
-
__send__(application_method, mod, group)
-
end
-
end
-
-
# @private
-
#
-
# Used internally to extend the singleton class of a single example's
-
# example group instance with modules using `include` and/or `extend`.
-
1
def configure_example(example)
-
9
singleton_group = example.example_group_instance.singleton_class
-
-
# We replace the metadata so that SharedExampleGroupModule#included
-
# has access to the example's metadata[:location].
-
9
singleton_group.with_replaced_metadata(example.metadata) do
-
9
modules = @include_modules.items_for(example.metadata)
-
9
modules.each do |mod|
-
safe_include(mod, example.example_group_instance.singleton_class)
-
end
-
-
9
MemoizedHelpers.define_helpers_on(singleton_group) unless modules.empty?
-
end
-
end
-
-
1
if RSpec::Support::RubyFeatures.module_prepends_supported?
-
# @private
-
1
def safe_prepend(mod, host)
-
host.__send__(:prepend, mod) unless host < mod
-
end
-
end
-
-
# @private
-
1
def requires=(paths)
-
3
directories = ['lib', default_path].select { |p| File.directory? p }
-
1
RSpec::Core::RubyProject.add_to_load_path(*directories)
-
2
paths.each { |path| require path }
-
1
@requires += paths
-
end
-
-
# @private
-
1
def in_project_source_dir_regex
-
regexes = project_source_dirs.map do |dir|
-
/\A#{Regexp.escape(File.expand_path(dir))}\//
-
end
-
-
Regexp.union(regexes)
-
end
-
-
# @private
-
1
if RUBY_VERSION.to_f >= 1.9
-
# @private
-
1
def safe_include(mod, host)
-
host.__send__(:include, mod) unless host < mod
-
end
-
-
# @private
-
1
def safe_extend(mod, host)
-
11
host.extend(mod) unless host.singleton_class < mod
-
end
-
else # for 1.8.7
-
# :nocov:
-
skipped
# @private
-
skipped
def safe_include(mod, host)
-
skipped
host.__send__(:include, mod) unless host.included_modules.include?(mod)
-
skipped
end
-
skipped
-
skipped
# @private
-
skipped
def safe_extend(mod, host)
-
skipped
host.extend(mod) unless (class << host; self; end).included_modules.include?(mod)
-
skipped
end
-
# :nocov:
-
end
-
-
# @private
-
1
def configure_mock_framework
-
1
RSpec::Core::ExampleGroup.__send__(:include, mock_framework)
-
1
conditionally_disable_mocks_monkey_patching
-
end
-
-
# @private
-
1
def configure_expectation_framework
-
1
expectation_frameworks.each do |framework|
-
1
RSpec::Core::ExampleGroup.__send__(:include, framework)
-
end
-
1
conditionally_disable_expectations_monkey_patching
-
end
-
-
# @private
-
1
def load_spec_files
-
# Note which spec files world is already aware of.
-
# This is generally only needed for when the user runs
-
# `ruby path/to/spec.rb` (and loads `rspec/autorun`) --
-
# in that case, the spec file was loaded by `ruby` and
-
# isn't loaded by us here so we only know about it because
-
# of an example group being registered in it.
-
1
RSpec.world.registered_example_group_files.each do |f|
-
loaded_spec_files << f # the registered files are already expended absolute paths
-
end
-
-
1
files_to_run.uniq.each do |f|
-
1
file = File.expand_path(f)
-
1
load file
-
1
loaded_spec_files << file
-
end
-
-
1
@spec_files_loaded = true
-
end
-
-
# @private
-
79
DEFAULT_FORMATTER = lambda { |string| string }
-
-
# Formats the docstring output using the block provided.
-
#
-
# @example
-
# # This will strip the descriptions of both examples and example
-
# # groups.
-
# RSpec.configure do |config|
-
# config.format_docstrings { |s| s.strip }
-
# end
-
1
def format_docstrings(&block)
-
@format_docstrings_block = block_given? ? block : DEFAULT_FORMATTER
-
end
-
-
# @private
-
1
def format_docstrings_block
-
78
@format_docstrings_block ||= DEFAULT_FORMATTER
-
end
-
-
# @private
-
# @macro [attach] delegate_to_ordering_manager
-
# @!method $1
-
1
def self.delegate_to_ordering_manager(*methods)
-
5
methods.each do |method|
-
6
define_method method do |*args, &block|
-
29
ordering_manager.__send__(method, *args, &block)
-
end
-
end
-
end
-
-
# @macro delegate_to_ordering_manager
-
#
-
# Sets the seed value and sets the default global ordering to random.
-
1
delegate_to_ordering_manager :seed=
-
-
# @macro delegate_to_ordering_manager
-
# Seed for random ordering (default: generated randomly each run).
-
#
-
# When you run specs with `--order random`, RSpec generates a random seed
-
# for the randomization and prints it to the `output_stream` (assuming
-
# you're using RSpec's built-in formatters). If you discover an ordering
-
# dependency (i.e. examples fail intermittently depending on order), set
-
# this (on Configuration or on the command line with `--seed`) to run
-
# using the same seed while you debug the issue.
-
#
-
# We recommend, actually, that you use the command line approach so you
-
# don't accidentally leave the seed encoded.
-
1
delegate_to_ordering_manager :seed
-
-
# @macro delegate_to_ordering_manager
-
#
-
# Sets the default global order and, if order is `'rand:<seed>'`, also
-
# sets the seed.
-
1
delegate_to_ordering_manager :order=
-
-
# @macro delegate_to_ordering_manager
-
# Registers a named ordering strategy that can later be
-
# used to order an example group's subgroups by adding
-
# `:order => <name>` metadata to the example group.
-
#
-
# @param name [Symbol] The name of the ordering.
-
# @yield Block that will order the given examples or example groups
-
# @yieldparam list [Array<RSpec::Core::Example>,
-
# Array<RSpec::Core::ExampleGroup>] The examples or groups to order
-
# @yieldreturn [Array<RSpec::Core::Example>,
-
# Array<RSpec::Core::ExampleGroup>] The re-ordered examples or groups
-
#
-
# @example
-
# RSpec.configure do |rspec|
-
# rspec.register_ordering :reverse do |list|
-
# list.reverse
-
# end
-
# end
-
#
-
# describe MyClass, :order => :reverse do
-
# # ...
-
# end
-
#
-
# @note Pass the symbol `:global` to set the ordering strategy that
-
# will be used to order the top-level example groups and any example
-
# groups that do not have declared `:order` metadata.
-
1
delegate_to_ordering_manager :register_ordering
-
-
# @private
-
1
delegate_to_ordering_manager :seed_used?, :ordering_registry
-
-
# Set Ruby warnings on or off.
-
1
def warnings=(value)
-
$VERBOSE = !!value
-
end
-
-
# @return [Boolean] Whether or not ruby warnings are enabled.
-
1
def warnings?
-
$VERBOSE
-
end
-
-
# @private
-
1
RAISE_ERROR_WARNING_NOTIFIER = lambda { |message| raise message }
-
-
# Turns warnings into errors. This can be useful when
-
# you want RSpec to run in a 'strict' no warning situation.
-
#
-
# @example
-
#
-
# RSpec.configure do |rspec|
-
# rspec.raise_on_warning = true
-
# end
-
1
def raise_on_warning=(value)
-
if value
-
RSpec::Support.warning_notifier = RAISE_ERROR_WARNING_NOTIFIER
-
else
-
RSpec::Support.warning_notifier = RSpec::Support::DEFAULT_WARNING_NOTIFIER
-
end
-
end
-
-
# Exposes the current running example via the named
-
# helper method. RSpec 2.x exposed this via `example`,
-
# but in RSpec 3.0, the example is instead exposed via
-
# an arg yielded to `it`, `before`, `let`, etc. However,
-
# some extension gems (such as Capybara) depend on the
-
# RSpec 2.x's `example` method, so this config option
-
# can be used to maintain compatibility.
-
#
-
# @param method_name [Symbol] the name of the helper method
-
#
-
# @example
-
#
-
# RSpec.configure do |rspec|
-
# rspec.expose_current_running_example_as :example
-
# end
-
#
-
# describe MyClass do
-
# before do
-
# # `example` can be used here because of the above config.
-
# do_something if example.metadata[:type] == "foo"
-
# end
-
# end
-
1
def expose_current_running_example_as(method_name)
-
ExposeCurrentExample.module_exec do
-
extend RSpec::SharedContext
-
let(method_name) { |ex| ex }
-
end
-
-
include ExposeCurrentExample
-
end
-
-
# @private
-
1
module ExposeCurrentExample; end
-
-
# Turns deprecation warnings into errors, in order to surface
-
# the full backtrace of the call site. This can be useful when
-
# you need more context to address a deprecation than the
-
# single-line call site normally provided.
-
#
-
# @example
-
#
-
# RSpec.configure do |rspec|
-
# rspec.raise_errors_for_deprecations!
-
# end
-
1
def raise_errors_for_deprecations!
-
self.deprecation_stream = Formatters::DeprecationFormatter::RaiseErrorStream.new
-
end
-
-
# Enables zero monkey patching mode for RSpec. It removes monkey
-
# patching of the top-level DSL methods (`describe`,
-
# `shared_examples_for`, etc) onto `main` and `Module`, instead
-
# requiring you to prefix these methods with `RSpec.`. It enables
-
# expect-only syntax for rspec-mocks and rspec-expectations. It
-
# simply disables monkey patching on whatever pieces of RSpec
-
# the user is using.
-
#
-
# @note It configures rspec-mocks and rspec-expectations only
-
# if the user is using those (either explicitly or implicitly
-
# by not setting `mock_with` or `expect_with` to anything else).
-
#
-
# @note If the user uses this options with `mock_with :mocha`
-
# (or similiar) they will still have monkey patching active
-
# in their test environment from mocha.
-
#
-
# @example
-
#
-
# # It disables all monkey patching.
-
# RSpec.configure do |config|
-
# config.disable_monkey_patching!
-
# end
-
#
-
# # Is an equivalent to
-
# RSpec.configure do |config|
-
# config.expose_dsl_globally = false
-
#
-
# config.mock_with :rspec do |mocks|
-
# mocks.syntax = :expect
-
# mocks.patch_marshal_to_support_partial_doubles = false
-
# end
-
#
-
# config.mock_with :rspec do |expectations|
-
# expectations.syntax = :expect
-
# end
-
# end
-
1
def disable_monkey_patching!
-
self.expose_dsl_globally = false
-
self.disable_monkey_patching = true
-
conditionally_disable_mocks_monkey_patching
-
conditionally_disable_expectations_monkey_patching
-
end
-
-
# @private
-
1
attr_accessor :disable_monkey_patching
-
-
# Defines a callback that can assign derived metadata values.
-
#
-
# @param filters [Array<Symbol>, Hash] metadata filters that determine
-
# which example or group metadata hashes the callback will be triggered
-
# for. If none are given, the callback will be run against the metadata
-
# hashes of all groups and examples.
-
# @yieldparam metadata [Hash] original metadata hash from an example or
-
# group. Mutate this in your block as needed.
-
#
-
# @example
-
# RSpec.configure do |config|
-
# # Tag all groups and examples in the spec/unit directory with
-
# # :type => :unit
-
# config.define_derived_metadata(:file_path => %r{/spec/unit/}) do |metadata|
-
# metadata[:type] = :unit
-
# end
-
# end
-
1
def define_derived_metadata(*filters, &block)
-
meta = Metadata.build_hash_from(filters, :warn_about_example_group_filtering)
-
@derived_metadata_blocks.append(block, meta)
-
end
-
-
# @private
-
1
def apply_derived_metadata_to(metadata)
-
21
@derived_metadata_blocks.items_for(metadata).each do |block|
-
block.call(metadata)
-
end
-
end
-
-
# Defines a `before` hook. See {Hooks#before} for full docs.
-
#
-
# This method differs from {Hooks#before} in only one way: it supports
-
# the `:suite` scope. Hooks with the `:suite` scope will be run once before
-
# the first example of the entire suite is executed.
-
#
-
# @see #prepend_before
-
# @see #after
-
# @see #append_after
-
1
def before(*args, &block)
-
handle_suite_hook(args, @before_suite_hooks, :push,
-
Hooks::BeforeHook, block) || super(*args, &block)
-
end
-
1
alias_method :append_before, :before
-
-
# Adds `block` to the start of the list of `before` blocks in the same
-
# scope (`:example`, `:context`, or `:suite`), in contrast to {#before},
-
# which adds the hook to the end of the list.
-
#
-
# See {Hooks#before} for full `before` hook docs.
-
#
-
# This method differs from {Hooks#prepend_before} in only one way: it supports
-
# the `:suite` scope. Hooks with the `:suite` scope will be run once before
-
# the first example of the entire suite is executed.
-
#
-
# @see #before
-
# @see #after
-
# @see #append_after
-
1
def prepend_before(*args, &block)
-
handle_suite_hook(args, @before_suite_hooks, :unshift,
-
Hooks::BeforeHook, block) || super(*args, &block)
-
end
-
-
# Defines a `after` hook. See {Hooks#after} for full docs.
-
#
-
# This method differs from {Hooks#after} in only one way: it supports
-
# the `:suite` scope. Hooks with the `:suite` scope will be run once after
-
# the last example of the entire suite is executed.
-
#
-
# @see #append_after
-
# @see #before
-
# @see #prepend_before
-
1
def after(*args, &block)
-
handle_suite_hook(args, @after_suite_hooks, :unshift,
-
Hooks::AfterHook, block) || super(*args, &block)
-
end
-
1
alias_method :prepend_after, :after
-
-
# Adds `block` to the end of the list of `after` blocks in the same
-
# scope (`:example`, `:context`, or `:suite`), in contrast to {#after},
-
# which adds the hook to the start of the list.
-
#
-
# See {Hooks#after} for full `after` hook docs.
-
#
-
# This method differs from {Hooks#append_after} in only one way: it supports
-
# the `:suite` scope. Hooks with the `:suite` scope will be run once after
-
# the last example of the entire suite is executed.
-
#
-
# @see #append_after
-
# @see #before
-
# @see #prepend_before
-
1
def append_after(*args, &block)
-
handle_suite_hook(args, @after_suite_hooks, :push,
-
Hooks::AfterHook, block) || super(*args, &block)
-
end
-
-
# @private
-
1
def with_suite_hooks
-
1
return yield if dry_run?
-
-
1
hook_context = SuiteHookContext.new
-
1
begin
-
1
run_hooks_with(@before_suite_hooks, hook_context)
-
1
yield
-
ensure
-
1
run_hooks_with(@after_suite_hooks, hook_context)
-
end
-
end
-
-
# @private
-
# Holds the various registered hooks. Here we use a FilterableItemRepository
-
# implementation that is specifically optimized for the read/write patterns
-
# of the config object.
-
1
def hooks
-
21
@hooks ||= HookCollections.new(self, FilterableItemRepository::QueryOptimized)
-
end
-
-
# Invokes block before defining an example group
-
1
def on_example_group_definition(&block)
-
on_example_group_definition_callbacks << block
-
end
-
-
# @api private
-
# Returns an array of blocks to call before defining an example group
-
1
def on_example_group_definition_callbacks
-
1
@on_example_group_definition_callbacks ||= []
-
end
-
-
1
private
-
-
1
def handle_suite_hook(args, collection, append_or_prepend, hook_type, block)
-
scope, meta = *args
-
return nil unless scope == :suite
-
-
if meta
-
# TODO: in RSpec 4, consider raising an error here.
-
# We warn only for backwards compatibility.
-
RSpec.warn_with "WARNING: `:suite` hooks do not support metadata since " \
-
"they apply to the suite as a whole rather than " \
-
"any individual example or example group that has metadata. " \
-
"The metadata you have provided (#{meta.inspect}) will be ignored."
-
end
-
-
collection.__send__(append_or_prepend, hook_type.new(block, {}))
-
end
-
-
1
def run_hooks_with(hooks, hook_context)
-
2
hooks.each { |h| h.run(hook_context) }
-
end
-
-
1
def get_files_to_run(paths)
-
1
files = FlatMap.flat_map(paths_to_check(paths)) do |path|
-
1
path = path.gsub(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
-
1
File.directory?(path) ? gather_directories(path) : extract_location(path)
-
end.sort.uniq
-
-
1
return files unless only_failures?
-
relative_files = files.map { |f| Metadata.relative_path(File.expand_path f) }
-
intersection = (relative_files & spec_files_with_failures.to_a)
-
intersection.empty? ? files : intersection
-
end
-
-
1
def paths_to_check(paths)
-
1
return paths if pattern_might_load_specs_from_vendored_dirs?
-
paths + [Dir.getwd]
-
end
-
-
1
def pattern_might_load_specs_from_vendored_dirs?
-
1
pattern.split(File::SEPARATOR).first.include?('**')
-
end
-
-
1
def gather_directories(path)
-
include_files = get_matching_files(path, pattern)
-
exclude_files = get_matching_files(path, exclude_pattern)
-
(include_files - exclude_files).sort.uniq
-
end
-
-
1
def get_matching_files(path, pattern)
-
Dir[file_glob_from(path, pattern)].map { |file| File.expand_path(file) }
-
end
-
-
1
def file_glob_from(path, pattern)
-
stripped = "{#{pattern.gsub(/\s*,\s*/, ',')}}"
-
return stripped if pattern =~ /^(\.\/)?#{Regexp.escape path}/ || absolute_pattern?(pattern)
-
File.join(path, stripped)
-
end
-
-
1
if RSpec::Support::OS.windows?
-
# :nocov:
-
skipped
def absolute_pattern?(pattern)
-
skipped
pattern =~ /\A[A-Z]:\\/ || windows_absolute_network_path?(pattern)
-
skipped
end
-
skipped
-
skipped
def windows_absolute_network_path?(pattern)
-
skipped
return false unless ::File::ALT_SEPARATOR
-
skipped
pattern.start_with?(::File::ALT_SEPARATOR + ::File::ALT_SEPARATOR)
-
skipped
end
-
# :nocov:
-
else
-
1
def absolute_pattern?(pattern)
-
pattern.start_with?(File::Separator)
-
end
-
end
-
-
1
def extract_location(path)
-
1
match = /^(.*?)((?:\:\d+)+)$/.match(path)
-
-
1
if match
-
captures = match.captures
-
path = captures[0]
-
lines = captures[1][1..-1].split(":").map(&:to_i)
-
filter_manager.add_location path, lines
-
else
-
1
path, scoped_ids = Example.parse_id(path)
-
1
filter_manager.add_ids(path, scoped_ids.split(/\s*,\s*/)) if scoped_ids
-
end
-
-
1
return [] if path == default_path
-
1
path
-
end
-
-
1
def command
-
1
$0.split(File::SEPARATOR).last
-
end
-
-
1
def value_for(key)
-
330
@preferred_options.fetch(key) { yield }
-
end
-
-
1
def define_built_in_hooks
-
1
around(:example, :aggregate_failures => true) do |procsy|
-
begin
-
aggregate_failures(nil, :hide_backtrace => true, &procsy)
-
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => exception
-
procsy.example.set_aggregate_failures_exception(exception)
-
end
-
end
-
end
-
-
1
def assert_no_example_groups_defined(config_option)
-
2
return unless RSpec.world.example_groups.any?
-
-
raise MustBeConfiguredBeforeExampleGroupsError.new(
-
"RSpec's #{config_option} configuration option must be configured before " \
-
"any example groups are defined, but you have already defined a group."
-
)
-
end
-
-
1
def output_to_tty?(output=output_stream)
-
tty? || (output.respond_to?(:tty?) && output.tty?)
-
end
-
-
1
def conditionally_disable_mocks_monkey_patching
-
1
return unless disable_monkey_patching && rspec_mocks_loaded?
-
-
RSpec::Mocks.configuration.tap do |config|
-
config.syntax = :expect
-
config.patch_marshal_to_support_partial_doubles = false
-
end
-
end
-
-
1
def conditionally_disable_expectations_monkey_patching
-
1
return unless disable_monkey_patching && rspec_expectations_loaded?
-
-
RSpec::Expectations.configuration.syntax = :expect
-
end
-
-
1
def rspec_mocks_loaded?
-
defined?(RSpec::Mocks.configuration)
-
end
-
-
1
def rspec_expectations_loaded?
-
defined?(RSpec::Expectations.configuration)
-
end
-
-
1
def update_pattern_attr(name, value)
-
if @spec_files_loaded
-
RSpec.warning "Configuring `#{name}` to #{value} has no effect since " \
-
"RSpec has already loaded the spec files."
-
end
-
-
instance_variable_set(:"@#{name}", value)
-
@files_to_run = nil
-
end
-
-
1
def clear_values_derived_from_example_status_persistence_file_path
-
@last_run_statuses = nil
-
@spec_files_with_failures = nil
-
end
-
end
-
# rubocop:enable Metrics/ClassLength
-
end
-
end
-
1
require 'erb'
-
1
require 'shellwords'
-
-
1
module RSpec
-
1
module Core
-
# Responsible for utilizing externally provided configuration options,
-
# whether via the command line, `.rspec`, `~/.rspec`, `.rspec-local`
-
# or a custom options file.
-
1
class ConfigurationOptions
-
# @param args [Array<String>] command line arguments
-
1
def initialize(args)
-
1
@args = args.dup
-
1
organize_options
-
end
-
-
# Updates the provided {Configuration} instance based on the provided
-
# external configuration options.
-
#
-
# @param config [Configuration] the configuration instance to update
-
1
def configure(config)
-
1
process_options_into config
-
1
configure_filter_manager config.filter_manager
-
1
load_formatters_into config
-
end
-
-
# @api private
-
# Updates the provided {FilterManager} based on the filter options.
-
# @param filter_manager [FilterManager] instance to update
-
1
def configure_filter_manager(filter_manager)
-
1
@filter_manager_options.each do |command, value|
-
filter_manager.__send__ command, value
-
end
-
end
-
-
# @return [Hash] the final merged options, drawn from all external sources
-
1
attr_reader :options
-
-
1
private
-
-
1
def organize_options
-
1
@filter_manager_options = []
-
-
1
@options = (file_options << command_line_options << env_options).each do |opts|
-
5
@filter_manager_options << [:include, opts.delete(:inclusion_filter)] if opts.key?(:inclusion_filter)
-
5
@filter_manager_options << [:exclude, opts.delete(:exclusion_filter)] if opts.key?(:exclusion_filter)
-
end
-
-
1
@options = @options.inject(:libs => [], :requires => []) do |hash, opts|
-
5
hash.merge(opts) do |key, oldval, newval|
-
2
[:libs, :requires].include?(key) ? oldval + newval : newval
-
end
-
end
-
end
-
-
1
UNFORCED_OPTIONS = Set.new([
-
:requires, :profile, :drb, :libs, :files_or_directories_to_run,
-
:full_description, :full_backtrace, :tty
-
])
-
-
1
UNPROCESSABLE_OPTIONS = Set.new([:formatters])
-
-
1
def force?(key)
-
4
!UNFORCED_OPTIONS.include?(key)
-
end
-
-
1
def order(keys)
-
1
OPTIONS_ORDER.reverse_each do |key|
-
9
keys.unshift(key) if keys.delete(key)
-
end
-
1
keys
-
end
-
-
1
OPTIONS_ORDER = [
-
# It's important to set this before anything that might issue a
-
# deprecation (or otherwise access the reporter).
-
:deprecation_stream,
-
-
# load paths depend on nothing, but must be set before `requires`
-
# to support load-path-relative requires.
-
:libs,
-
-
# `files_or_directories_to_run` uses `default_path` so it must be
-
# set before it.
-
:default_path, :only_failures,
-
-
# These must be set before `requires` to support checking
-
# `config.files_to_run` from within `spec_helper.rb` when a
-
# `-rspec_helper` option is used.
-
:files_or_directories_to_run, :pattern, :exclude_pattern,
-
-
# Necessary so that the `--seed` option is applied before requires,
-
# in case required files do something with the provided seed.
-
# (such as seed global randomization with it).
-
:order,
-
-
# In general, we want to require the specified files as early as
-
# possible. The `--require` option is specifically intended to allow
-
# early requires. For later requires, they can just put the require in
-
# their spec files, but `--require` provides a unique opportunity for
-
# users to instruct RSpec to load an extension file early for maximum
-
# flexibility.
-
:requires
-
]
-
-
1
def process_options_into(config)
-
6
opts = options.reject { |k, _| UNPROCESSABLE_OPTIONS.include? k }
-
-
1
order(opts.keys).each do |key|
-
4
force?(key) ? config.force(key => opts[key]) : config.__send__("#{key}=", opts[key])
-
end
-
end
-
-
1
def load_formatters_into(config)
-
2
options[:formatters].each { |pair| config.add_formatter(*pair) } if options[:formatters]
-
end
-
-
1
def file_options
-
1
custom_options_file ? [custom_options] : [global_options, project_options, local_options]
-
end
-
-
1
def env_options
-
1
return {} unless ENV['SPEC_OPTS']
-
-
parse_args_ignoring_files_or_dirs_to_run(
-
Shellwords.split(ENV["SPEC_OPTS"]),
-
"ENV['SPEC_OPTS']"
-
)
-
end
-
-
1
def command_line_options
-
2
@command_line_options ||= Parser.parse(@args)
-
end
-
-
1
def custom_options
-
options_from(custom_options_file)
-
end
-
-
1
def local_options
-
1
@local_options ||= options_from(local_options_file)
-
end
-
-
1
def project_options
-
1
@project_options ||= options_from(project_options_file)
-
end
-
-
1
def global_options
-
1
@global_options ||= options_from(global_options_file)
-
end
-
-
1
def options_from(path)
-
3
args = args_from_options_file(path)
-
3
parse_args_ignoring_files_or_dirs_to_run(args, path)
-
end
-
-
1
def parse_args_ignoring_files_or_dirs_to_run(args, source)
-
3
options = Parser.parse(args, source)
-
3
options.delete(:files_or_directories_to_run)
-
3
options
-
end
-
-
1
def args_from_options_file(path)
-
3
return [] unless path && File.exist?(path)
-
1
config_string = options_file_as_erb_string(path)
-
1
FlatMap.flat_map(config_string.split(/\n+/), &:shellsplit)
-
end
-
-
1
def options_file_as_erb_string(path)
-
1
ERB.new(File.read(path), nil, '-').result(binding)
-
end
-
-
1
def custom_options_file
-
1
command_line_options[:custom_options_file]
-
end
-
-
1
def project_options_file
-
1
"./.rspec"
-
end
-
-
1
def local_options_file
-
1
"./.rspec-local"
-
end
-
-
1
def global_options_file
-
1
File.join(File.expand_path("~"), ".rspec")
-
rescue ArgumentError
-
RSpec.warning "Unable to find ~/.rspec because the HOME environment variable is not set"
-
nil
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Core
-
# DSL defines methods to group examples, most notably `describe`,
-
# and exposes them as class methods of {RSpec}. They can also be
-
# exposed globally (on `main` and instances of `Module`) through
-
# the {Configuration} option `expose_dsl_globally`.
-
#
-
# By default the methods `describe`, `context` and `example_group`
-
# are exposed. These methods define a named context for one or
-
# more examples. The given block is evaluated in the context of
-
# a generated subclass of {RSpec::Core::ExampleGroup}.
-
#
-
# ## Examples:
-
#
-
# RSpec.describe "something" do
-
# context "when something is a certain way" do
-
# it "does something" do
-
# # example code goes here
-
# end
-
# end
-
# end
-
#
-
# @see ExampleGroup
-
# @see ExampleGroup.example_group
-
1
module DSL
-
# @private
-
1
def self.example_group_aliases
-
15
@example_group_aliases ||= []
-
end
-
-
# @private
-
1
def self.exposed_globally?
-
8
@exposed_globally ||= false
-
end
-
-
# @private
-
1
def self.expose_example_group_alias(name)
-
7
return if example_group_aliases.include?(name)
-
-
7
example_group_aliases << name
-
-
14
(class << RSpec; self; end).__send__(:define_method, name) do |*args, &example_group_block|
-
1
RSpec.world.register RSpec::Core::ExampleGroup.__send__(name, *args, &example_group_block)
-
end
-
-
7
expose_example_group_alias_globally(name) if exposed_globally?
-
end
-
-
1
class << self
-
# @private
-
1
attr_accessor :top_level
-
end
-
-
# Adds the describe method to Module and the top level binding.
-
# @api private
-
1
def self.expose_globally!
-
1
return if exposed_globally?
-
-
1
example_group_aliases.each do |method_name|
-
7
expose_example_group_alias_globally(method_name)
-
end
-
-
1
@exposed_globally = true
-
end
-
-
# Removes the describe method from Module and the top level binding.
-
# @api private
-
1
def self.remove_globally!
-
return unless exposed_globally?
-
-
example_group_aliases.each do |method_name|
-
change_global_dsl { undef_method method_name }
-
end
-
-
@exposed_globally = false
-
end
-
-
# @private
-
1
def self.expose_example_group_alias_globally(method_name)
-
7
change_global_dsl do
-
14
remove_method(method_name) if method_defined?(method_name)
-
15
define_method(method_name) { |*a, &b| ::RSpec.__send__(method_name, *a, &b) }
-
end
-
end
-
-
# @private
-
1
def self.change_global_dsl(&changes)
-
16
(class << top_level; self; end).class_exec(&changes)
-
8
Module.class_exec(&changes)
-
end
-
end
-
end
-
end
-
-
# Capture main without an eval.
-
1
::RSpec::Core::DSL.top_level = self
-
1
module RSpec
-
1
module Core
-
# Wrapper for an instance of a subclass of {ExampleGroup}. An instance of
-
# `RSpec::Core::Example` is returned by example definition methods
-
# such as {ExampleGroup.it it} and is yielded to the {ExampleGroup.it it},
-
# {Hooks#before before}, {Hooks#after after}, {Hooks#around around},
-
# {MemoizedHelpers::ClassMethods#let let} and
-
# {MemoizedHelpers::ClassMethods#subject subject} blocks.
-
#
-
# This allows us to provide rich metadata about each individual
-
# example without adding tons of methods directly to the ExampleGroup
-
# that users may inadvertantly redefine.
-
#
-
# Useful for configuring logging and/or taking some action based
-
# on the state of an example's metadata.
-
#
-
# @example
-
#
-
# RSpec.configure do |config|
-
# config.before do |example|
-
# log example.description
-
# end
-
#
-
# config.after do |example|
-
# log example.description
-
# end
-
#
-
# config.around do |example|
-
# log example.description
-
# example.run
-
# end
-
# end
-
#
-
# shared_examples "auditable" do
-
# it "does something" do
-
# log "#{example.full_description}: #{auditable.inspect}"
-
# auditable.should do_something
-
# end
-
# end
-
#
-
# @see ExampleGroup
-
# @note Example blocks are evaluated in the context of an instance
-
# of an `ExampleGroup`, not in the context of an instance of `Example`.
-
1
class Example
-
# @private
-
#
-
# Used to define methods that delegate to this example's metadata.
-
1
def self.delegate_to_metadata(key)
-
159
define_method(key) { @metadata[key] }
-
end
-
-
# @return [ExecutionResult] represents the result of running this example.
-
1
delegate_to_metadata :execution_result
-
# @return [String] the relative path to the file where this example was
-
# defined.
-
1
delegate_to_metadata :file_path
-
# @return [String] the full description (including the docstrings of
-
# all parent example groups).
-
1
delegate_to_metadata :full_description
-
# @return [String] the exact source location of this example in a form
-
# like `./path/to/spec.rb:17`
-
1
delegate_to_metadata :location
-
# @return [Boolean] flag that indicates that the example is not expected
-
# to pass. It will be run and will either have a pending result (if a
-
# failure occurs) or a failed result (if no failure occurs).
-
1
delegate_to_metadata :pending
-
# @return [Boolean] flag that will cause the example to not run.
-
# The {ExecutionResult} status will be `:pending`.
-
1
delegate_to_metadata :skip
-
-
# Returns the string submitted to `example` or its aliases (e.g.
-
# `specify`, `it`, etc). If no string is submitted (e.g.
-
# `it { is_expected.to do_something }`) it returns the message generated
-
# by the matcher if there is one, otherwise returns a message including
-
# the location of the example.
-
1
def description
-
45
description = if metadata[:description].to_s.empty?
-
location_description
-
else
-
45
metadata[:description]
-
end
-
-
45
RSpec.configuration.format_docstrings_block.call(description)
-
end
-
-
# Returns a description of the example that always includes the location.
-
1
def inspect_output
-
9
inspect_output = "\"#{description}\""
-
9
unless metadata[:description].to_s.empty?
-
9
inspect_output << " (#{location})"
-
end
-
9
inspect_output
-
end
-
-
# Returns the location-based argument that can be passed to the `rspec` command to rerun this example.
-
1
def location_rerun_argument
-
@location_rerun_argument ||= begin
-
loaded_spec_files = RSpec.configuration.loaded_spec_files
-
-
Metadata.ascending(metadata) do |meta|
-
return meta[:location] if loaded_spec_files.include?(meta[:absolute_file_path])
-
end
-
end
-
end
-
-
# Returns the location-based argument that can be passed to the `rspec` command to rerun this example.
-
#
-
# @deprecated Use {#location_rerun_argument} instead.
-
# @note If there are multiple examples identified by this location, they will use {#id}
-
# to rerun instead, but this method will still return the location (that's why it is deprecated!).
-
1
def rerun_argument
-
location_rerun_argument
-
end
-
-
# @return [String] the unique id of this example. Pass
-
# this at the command line to re-run this exact example.
-
1
def id
-
10
@id ||= Metadata.id_from(metadata)
-
end
-
-
# @private
-
1
def self.parse_id(id)
-
# http://rubular.com/r/OMZSAPcAfn
-
1
id.match(/\A(.*?)(?:\[([\d\s:,]+)\])?\z/).captures
-
end
-
-
# Duplicates the example and overrides metadata with the provided
-
# hash.
-
#
-
# @param metadata_overrides [Hash] the hash to override the example metadata
-
# @return [Example] a duplicate of the example with modified metadata
-
1
def duplicate_with(metadata_overrides={})
-
new_metadata = metadata.clone.merge(metadata_overrides)
-
-
RSpec::Core::Metadata::RESERVED_KEYS.each do |reserved_key|
-
new_metadata.delete reserved_key
-
end
-
-
# don't clone the example group because the new example
-
# must belong to the same example group (not a clone).
-
Example.new(example_group, description.clone,
-
new_metadata, new_metadata[:block])
-
end
-
-
# @attr_reader
-
#
-
# Returns the first exception raised in the context of running this
-
# example (nil if no exception is raised).
-
1
attr_reader :exception
-
-
# @attr_reader
-
#
-
# Returns the metadata object associated with this example.
-
1
attr_reader :metadata
-
-
# @attr_reader
-
# @private
-
#
-
# Returns the example_group_instance that provides the context for
-
# running this example.
-
1
attr_reader :example_group_instance
-
-
# @attr
-
# @private
-
1
attr_accessor :clock
-
-
# Creates a new instance of Example.
-
# @param example_group_class [Class] the subclass of ExampleGroup in which
-
# this Example is declared
-
# @param description [String] the String passed to the `it` method (or
-
# alias)
-
# @param user_metadata [Hash] additional args passed to `it` to be used as
-
# metadata
-
# @param example_block [Proc] the block of code that represents the
-
# example
-
# @api private
-
1
def initialize(example_group_class, description, user_metadata, example_block=nil)
-
10
@example_group_class = example_group_class
-
10
@example_block = example_block
-
-
10
@metadata = Metadata::ExampleHash.create(
-
@example_group_class.metadata, user_metadata,
-
example_group_class.method(:next_runnable_index_for),
-
description, example_block
-
)
-
-
# This should perhaps be done in `Metadata::ExampleHash.create`,
-
# but the logic there has no knowledge of `RSpec.world` and we
-
# want to keep it that way. It's easier to just assign it here.
-
10
@metadata[:last_run_status] = RSpec.configuration.last_run_statuses[id]
-
-
10
@example_group_instance = @exception = nil
-
10
@clock = RSpec::Core::Time
-
10
@reporter = RSpec::Core::NullReporter
-
end
-
-
# Provide a human-readable representation of this class
-
1
def inspect
-
18
"#<#{self.class.name} #{description.inspect}>"
-
end
-
1
alias to_s inspect
-
-
# @return [RSpec::Core::Reporter] the current reporter for the example
-
1
attr_reader :reporter
-
-
# Returns the example group class that provides the context for running
-
# this example.
-
1
def example_group
-
9
@example_group_class
-
end
-
-
1
alias_method :pending?, :pending
-
1
alias_method :skipped?, :skip
-
-
# @api private
-
# instance_execs the block passed to the constructor in the context of
-
# the instance of {ExampleGroup}.
-
# @param example_group_instance the instance of an ExampleGroup subclass
-
1
def run(example_group_instance, reporter)
-
9
@example_group_instance = example_group_instance
-
9
@reporter = reporter
-
9
hooks.register_global_singleton_context_hooks(self, RSpec.configuration.hooks)
-
9
RSpec.configuration.configure_example(self)
-
9
RSpec.current_example = self
-
-
9
start(reporter)
-
9
Pending.mark_pending!(self, pending) if pending?
-
-
9
begin
-
9
if skipped?
-
Pending.mark_pending! self, skip
-
elsif !RSpec.configuration.dry_run?
-
9
with_around_and_singleton_context_hooks do
-
9
begin
-
9
run_before_example
-
9
@example_group_instance.instance_exec(self, &@example_block)
-
-
9
if pending?
-
Pending.mark_fixed! self
-
-
raise Pending::PendingExampleFixedError,
-
'Expected example to fail since it is pending, but it passed.',
-
[location]
-
end
-
rescue Pending::SkipDeclaredInExample
-
# no-op, required metadata has already been set by the `skip`
-
# method.
-
rescue AllExceptionsExcludingDangerousOnesOnRubiesThatAllowIt => e
-
set_exception(e)
-
ensure
-
9
run_after_example
-
end
-
end
-
end
-
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e
-
set_exception(e)
-
ensure
-
9
@example_group_instance = nil # if you love something... let it go
-
end
-
-
9
finish(reporter)
-
ensure
-
9
execution_result.ensure_timing_set(clock)
-
9
RSpec.current_example = nil
-
end
-
-
1
if RSpec::Support::Ruby.jruby? || RUBY_VERSION.to_f < 1.9
-
# :nocov:
-
skipped
# For some reason, rescuing `Support::AllExceptionsExceptOnesWeMustNotRescue`
-
skipped
# in place of `Exception` above can cause the exit status to be the wrong
-
skipped
# thing. I have no idea why. See:
-
skipped
# https://github.com/rspec/rspec-core/pull/2063#discussion_r38284978
-
skipped
# @private
-
skipped
AllExceptionsExcludingDangerousOnesOnRubiesThatAllowIt = Exception
-
# :nocov:
-
else
-
# @private
-
1
AllExceptionsExcludingDangerousOnesOnRubiesThatAllowIt = Support::AllExceptionsExceptOnesWeMustNotRescue
-
end
-
-
# Wraps both a `Proc` and an {Example} for use in {Hooks#around
-
# around} hooks. In around hooks we need to yield this special
-
# kind of object (rather than the raw {Example}) because when
-
# there are multiple `around` hooks we have to wrap them recursively.
-
#
-
# @example
-
#
-
# RSpec.configure do |c|
-
# c.around do |ex| # Procsy which wraps the example
-
# if ex.metadata[:key] == :some_value && some_global_condition
-
# raise "some message"
-
# end
-
# ex.run # run delegates to ex.call.
-
# end
-
# end
-
#
-
# @note This class also exposes the instance methods of {Example},
-
# proxying them through to the wrapped {Example} instance.
-
1
class Procsy
-
# The {Example} instance.
-
1
attr_reader :example
-
-
1
Example.public_instance_methods(false).each do |name|
-
24
name_sym = name.to_sym
-
24
next if name_sym == :run || name_sym == :inspect || name_sym == :to_s
-
-
21
define_method(name) { |*a, &b| @example.__send__(name, *a, &b) }
-
end
-
-
1
Proc.public_instance_methods(false).each do |name|
-
16
name_sym = name.to_sym
-
16
next if name_sym == :call || name_sym == :inspect || name_sym == :to_s || name_sym == :to_proc
-
-
12
define_method(name) { |*a, &b| @proc.__send__(name, *a, &b) }
-
end
-
-
# Calls the proc and notes that the example has been executed.
-
1
def call(*args, &block)
-
@executed = true
-
@proc.call(*args, &block)
-
end
-
1
alias run call
-
-
# Provides a wrapped proc that will update our `executed?` state when
-
# executed.
-
1
def to_proc
-
method(:call).to_proc
-
end
-
-
1
def initialize(example, &block)
-
@example = example
-
@proc = block
-
@executed = false
-
end
-
-
# @private
-
1
def wrap(&block)
-
self.class.new(example, &block)
-
end
-
-
# Indicates whether or not the around hook has executed the example.
-
1
def executed?
-
@executed
-
end
-
-
# @private
-
1
def inspect
-
@example.inspect.gsub('Example', 'ExampleProcsy')
-
end
-
end
-
-
# @private
-
#
-
# The exception that will be displayed to the user -- either the failure of
-
# the example or the `pending_exception` if the example is pending.
-
1
def display_exception
-
@exception || execution_result.pending_exception
-
end
-
-
# @private
-
#
-
# Assigns the exception that will be displayed to the user -- either the failure of
-
# the example or the `pending_exception` if the example is pending.
-
1
def display_exception=(ex)
-
if pending? && !(Pending::PendingExampleFixedError === ex)
-
@exception = nil
-
execution_result.pending_fixed = false
-
execution_result.pending_exception = ex
-
else
-
@exception = ex
-
end
-
end
-
-
# rubocop:disable Style/AccessorMethodName
-
-
# @private
-
#
-
# Used internally to set an exception in an after hook, which
-
# captures the exception but doesn't raise it.
-
1
def set_exception(exception)
-
return self.display_exception = exception unless display_exception
-
-
unless RSpec::Core::MultipleExceptionError === display_exception
-
self.display_exception = RSpec::Core::MultipleExceptionError.new(display_exception)
-
end
-
-
display_exception.add exception
-
end
-
-
# @private
-
#
-
# Used to set the exception when `aggregate_failures` fails.
-
1
def set_aggregate_failures_exception(exception)
-
return set_exception(exception) unless display_exception
-
-
exception = RSpec::Core::MultipleExceptionError::InterfaceTag.for(exception)
-
exception.add display_exception
-
self.display_exception = exception
-
end
-
-
# rubocop:enable Style/AccessorMethodName
-
-
# @private
-
#
-
# Used internally to set an exception and fail without actually executing
-
# the example when an exception is raised in before(:context).
-
1
def fail_with_exception(reporter, exception)
-
start(reporter)
-
set_exception(exception)
-
finish(reporter)
-
end
-
-
# @private
-
#
-
# Used internally to skip without actually executing the example when
-
# skip is used in before(:context).
-
1
def skip_with_exception(reporter, exception)
-
start(reporter)
-
Pending.mark_skipped! self, exception.argument
-
finish(reporter)
-
end
-
-
# @private
-
1
def instance_exec(*args, &block)
-
5
@example_group_instance.instance_exec(*args, &block)
-
end
-
-
1
private
-
-
1
def hooks
-
36
example_group_instance.singleton_class.hooks
-
end
-
-
1
def with_around_example_hooks
-
18
hooks.run(:around, :example, self) { yield }
-
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e
-
set_exception(e)
-
end
-
-
1
def start(reporter)
-
9
reporter.example_started(self)
-
9
execution_result.started_at = clock.now
-
end
-
-
1
def finish(reporter)
-
9
pending_message = execution_result.pending_message
-
-
9
reporter.example_finished(self)
-
9
if @exception
-
record_finished :failed
-
execution_result.exception = @exception
-
reporter.example_failed self
-
false
-
9
elsif pending_message
-
record_finished :pending
-
execution_result.pending_message = pending_message
-
reporter.example_pending self
-
true
-
else
-
9
record_finished :passed
-
9
reporter.example_passed self
-
9
true
-
end
-
end
-
-
1
def record_finished(status)
-
9
execution_result.record_finished(status, clock.now)
-
end
-
-
1
def run_before_example
-
9
@example_group_instance.setup_mocks_for_rspec
-
9
hooks.run(:before, :example, self)
-
end
-
-
1
def with_around_and_singleton_context_hooks
-
9
singleton_context_hooks_host = example_group_instance.singleton_class
-
9
singleton_context_hooks_host.run_before_context_hooks(example_group_instance)
-
18
with_around_example_hooks { yield }
-
ensure
-
9
singleton_context_hooks_host.run_after_context_hooks(example_group_instance)
-
end
-
-
1
def run_after_example
-
9
assign_generated_description if defined?(::RSpec::Matchers)
-
9
hooks.run(:after, :example, self)
-
9
verify_mocks
-
ensure
-
9
@example_group_instance.teardown_mocks_for_rspec
-
end
-
-
1
def verify_mocks
-
9
@example_group_instance.verify_mocks_for_rspec if mocks_need_verification?
-
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e
-
set_exception(e)
-
end
-
-
1
def mocks_need_verification?
-
9
exception.nil? || execution_result.pending_fixed?
-
end
-
-
1
def assign_generated_description
-
9
if metadata[:description].empty? && (description = generate_description)
-
metadata[:description] = description
-
metadata[:full_description] << description
-
end
-
ensure
-
9
RSpec::Matchers.clear_generated_description
-
end
-
-
1
def generate_description
-
RSpec::Matchers.generated_description
-
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e
-
location_description + " (Got an error when generating description " \
-
"from matcher: #{e.class}: #{e.message} -- #{e.backtrace.first})"
-
end
-
-
1
def location_description
-
"example at #{location}"
-
end
-
-
# Represents the result of executing an example.
-
# Behaves like a hash for backwards compatibility.
-
1
class ExecutionResult
-
1
include HashImitatable
-
-
# @return [Symbol] `:passed`, `:failed` or `:pending`.
-
1
attr_accessor :status
-
-
# @return [Exception, nil] The failure, if there was one.
-
1
attr_accessor :exception
-
-
# @return [Time] When the example started.
-
1
attr_accessor :started_at
-
-
# @return [Time] When the example finished.
-
1
attr_accessor :finished_at
-
-
# @return [Float] How long the example took in seconds.
-
1
attr_accessor :run_time
-
-
# @return [String, nil] The reason the example was pending,
-
# or nil if the example was not pending.
-
1
attr_accessor :pending_message
-
-
# @return [Exception, nil] The exception triggered while
-
# executing the pending example. If no exception was triggered
-
# it would no longer get a status of `:pending` unless it was
-
# tagged with `:skip`.
-
1
attr_accessor :pending_exception
-
-
# @return [Boolean] For examples tagged with `:pending`,
-
# this indicates whether or not it now passes.
-
1
attr_accessor :pending_fixed
-
-
1
alias pending_fixed? pending_fixed
-
-
# @return [Boolean] Indicates if the example was completely skipped
-
# (typically done via `:skip` metadata or the `skip` method). Skipped examples
-
# will have a `:pending` result. A `:pending` result can also come from examples
-
# that were marked as `:pending`, which causes them to be run, and produces a
-
# `:failed` result if the example passes.
-
1
def example_skipped?
-
27
status == :pending && !pending_exception
-
end
-
-
# @api private
-
# Records the finished status of the example.
-
1
def record_finished(status, finished_at)
-
9
self.status = status
-
9
calculate_run_time(finished_at)
-
end
-
-
# @api private
-
# Populates finished_at and run_time if it has not yet been set
-
1
def ensure_timing_set(clock)
-
9
calculate_run_time(clock.now) unless finished_at
-
end
-
-
1
private
-
-
1
def calculate_run_time(finished_at)
-
9
self.finished_at = finished_at
-
9
self.run_time = (finished_at - started_at).to_f
-
end
-
-
# For backwards compatibility we present `status` as a string
-
# when presenting the legacy hash interface.
-
1
def hash_for_delegation
-
super.tap do |hash|
-
hash[:status] &&= status.to_s
-
end
-
end
-
-
1
def set_value(name, value)
-
value &&= value.to_sym if name == :status
-
super(name, value)
-
end
-
-
1
def get_value(name)
-
if name == :status
-
status.to_s if status
-
else
-
super
-
end
-
end
-
-
1
def issue_deprecation(_method_name, *_args)
-
RSpec.deprecate("Treating `metadata[:execution_result]` as a hash",
-
:replacement => "the attributes methods to access the data")
-
end
-
end
-
end
-
-
# @private
-
# Provides an execution context for before/after :suite hooks.
-
1
class SuiteHookContext < Example
-
1
def initialize
-
1
super(AnonymousExampleGroup, "", {})
-
1
@example_group_instance = AnonymousExampleGroup.new
-
end
-
-
# rubocop:disable Style/AccessorMethodName
-
-
# To ensure we don't silence errors.
-
1
def set_exception(exception)
-
raise exception
-
end
-
# rubocop:enable Style/AccessorMethodName
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support 'recursive_const_methods'
-
-
1
module RSpec
-
1
module Core
-
# rubocop:disable Metrics/ClassLength
-
# ExampleGroup and {Example} are the main structural elements of
-
# rspec-core. Consider this example:
-
#
-
# describe Thing do
-
# it "does something" do
-
# end
-
# end
-
#
-
# The object returned by `describe Thing` is a subclass of ExampleGroup.
-
# The object returned by `it "does something"` is an instance of Example,
-
# which serves as a wrapper for an instance of the ExampleGroup in which it
-
# is declared.
-
#
-
# Example group bodies (e.g. `describe` or `context` blocks) are evaluated
-
# in the context of a new subclass of ExampleGroup. Individual examples are
-
# evaluated in the context of an instance of the specific ExampleGroup
-
# subclass to which they belong.
-
#
-
# Besides the class methods defined here, there are other interesting macros
-
# defined in {Hooks}, {MemoizedHelpers::ClassMethods} and
-
# {SharedExampleGroup}. There are additional instance methods available to
-
# your examples defined in {MemoizedHelpers} and {Pending}.
-
1
class ExampleGroup
-
1
extend Hooks
-
-
1
include MemoizedHelpers
-
1
extend MemoizedHelpers::ClassMethods
-
1
include Pending
-
1
extend SharedExampleGroup
-
-
# @private
-
1
def self.idempotently_define_singleton_method(name, &definition)
-
48
(class << self; self; end).module_exec do
-
24
remove_method(name) if method_defined?(name) && instance_method(name).owner == self
-
24
define_method(name, &definition)
-
end
-
end
-
-
# @!group Metadata
-
-
# The [Metadata](Metadata) object associated with this group.
-
# @see Metadata
-
1
def self.metadata
-
137
@metadata ||= nil
-
end
-
-
# Temporarily replace the provided metadata.
-
# Intended primarily to allow an example group's singleton class
-
# to return the metadata of the example that it exists for. This
-
# is necessary for shared example group inclusion to work properly
-
# with singleton example groups.
-
# @private
-
1
def self.with_replaced_metadata(meta)
-
9
orig_metadata = metadata
-
9
@metadata = meta
-
9
yield
-
ensure
-
9
@metadata = orig_metadata
-
end
-
-
# @private
-
# @return [Metadata] belonging to the parent of a nested {ExampleGroup}
-
1
def self.superclass_metadata
-
11
@superclass_metadata ||= superclass.respond_to?(:metadata) ? superclass.metadata : nil
-
end
-
-
# @private
-
1
def self.delegate_to_metadata(*names)
-
1
names.each do |name|
-
22
idempotently_define_singleton_method(name) { metadata.fetch(name) }
-
end
-
end
-
-
1
delegate_to_metadata :described_class, :file_path, :location
-
-
# @return [String] the current example group description
-
1
def self.description
-
33
description = metadata[:description]
-
33
RSpec.configuration.format_docstrings_block.call(description)
-
end
-
-
# Returns the class or module passed to the `describe` method (or alias).
-
# Returns nil if the subject is not a class or module.
-
# @example
-
# describe Thing do
-
# it "does something" do
-
# described_class == Thing
-
# end
-
# end
-
#
-
1
def described_class
-
8
self.class.described_class
-
end
-
-
# @!endgroup
-
-
# @!group Defining Examples
-
-
# @private
-
# @macro [attach] define_example_method
-
# @!scope class
-
# @overload $1
-
# @overload $1(&example_implementation)
-
# @param example_implementation [Block] The implementation of the example.
-
# @overload $1(doc_string, *metadata_keys, metadata={})
-
# @param doc_string [String] The example's doc string.
-
# @param metadata [Hash] Metadata for the example.
-
# @param metadata_keys [Array<Symbol>] Metadata tags for the example.
-
# Will be transformed into hash entries with `true` values.
-
# @overload $1(doc_string, *metadata_keys, metadata={}, &example_implementation)
-
# @param doc_string [String] The example's doc string.
-
# @param metadata [Hash] Metadata for the example.
-
# @param metadata_keys [Array<Symbol>] Metadata tags for the example.
-
# Will be transformed into hash entries with `true` values.
-
# @param example_implementation [Block] The implementation of the example.
-
# @yield [Example] the example object
-
# @example
-
# $1 do
-
# end
-
#
-
# $1 "does something" do
-
# end
-
#
-
# $1 "does something", :slow, :uses_js do
-
# end
-
#
-
# $1 "does something", :with => 'additional metadata' do
-
# end
-
#
-
# $1 "does something" do |ex|
-
# # ex is the Example object that contains metadata about the example
-
# end
-
1
def self.define_example_method(name, extra_options={})
-
12
idempotently_define_singleton_method(name) do |*all_args, &block|
-
9
desc, *args = *all_args
-
-
9
options = Metadata.build_hash_from(args)
-
9
options.update(:skip => RSpec::Core::Pending::NOT_YET_IMPLEMENTED) unless block
-
9
options.update(extra_options)
-
-
9
example = RSpec::Core::Example.new(self, desc, options, block)
-
9
examples << example
-
9
example
-
end
-
end
-
-
# Defines an example within a group.
-
1
define_example_method :example
-
# Defines an example within a group.
-
# This is the primary API to define a code example.
-
1
define_example_method :it
-
# Defines an example within a group.
-
# Useful for when your docstring does not read well off of `it`.
-
# @example
-
# RSpec.describe MyClass do
-
# specify "#do_something is deprecated" do
-
# # ...
-
# end
-
# end
-
1
define_example_method :specify
-
-
# Shortcut to define an example with `:focus => true`.
-
# @see example
-
1
define_example_method :focus, :focus => true
-
# Shortcut to define an example with `:focus => true`.
-
# @see example
-
1
define_example_method :fexample, :focus => true
-
# Shortcut to define an example with `:focus => true`.
-
# @see example
-
1
define_example_method :fit, :focus => true
-
# Shortcut to define an example with `:focus => true`.
-
# @see example
-
1
define_example_method :fspecify, :focus => true
-
# Shortcut to define an example with `:skip => 'Temporarily skipped with xexample'`.
-
# @see example
-
1
define_example_method :xexample, :skip => 'Temporarily skipped with xexample'
-
# Shortcut to define an example with `:skip => 'Temporarily skipped with xit'`.
-
# @see example
-
1
define_example_method :xit, :skip => 'Temporarily skipped with xit'
-
# Shortcut to define an example with `:skip => 'Temporarily skipped with xspecify'`.
-
# @see example
-
1
define_example_method :xspecify, :skip => 'Temporarily skipped with xspecify'
-
# Shortcut to define an example with `:skip => true`
-
# @see example
-
1
define_example_method :skip, :skip => true
-
# Shortcut to define an example with `:pending => true`
-
# @see example
-
1
define_example_method :pending, :pending => true
-
-
# @!endgroup
-
-
# @!group Defining Example Groups
-
-
# @private
-
# @macro [attach] define_example_group_method
-
# @!scope class
-
# @overload $1
-
# @overload $1(&example_group_definition)
-
# @param example_group_definition [Block] The definition of the example group.
-
# @overload $1(doc_string, *metadata_keys, metadata={}, &example_implementation)
-
# @param doc_string [String] The group's doc string.
-
# @param metadata [Hash] Metadata for the group.
-
# @param metadata_keys [Array<Symbol>] Metadata tags for the group.
-
# Will be transformed into hash entries with `true` values.
-
# @param example_group_definition [Block] The definition of the example group.
-
#
-
# Generates a subclass of this example group which inherits
-
# everything except the examples themselves.
-
#
-
# @example
-
#
-
# RSpec.describe "something" do # << This describe method is defined in
-
# # << RSpec::Core::DSL, included in the
-
# # << global namespace (optional)
-
# before do
-
# do_something_before
-
# end
-
#
-
# let(:thing) { Thing.new }
-
#
-
# $1 "attribute (of something)" do
-
# # examples in the group get the before hook
-
# # declared above, and can access `thing`
-
# end
-
# end
-
#
-
# @see DSL#describe
-
1
def self.define_example_group_method(name, metadata={})
-
7
idempotently_define_singleton_method(name) do |*args, &example_group_block|
-
11
thread_data = RSpec::Support.thread_local_data
-
11
top_level = self == ExampleGroup
-
-
11
if top_level
-
1
if thread_data[:in_example_group]
-
raise "Creating an isolated context from within a context is " \
-
"not allowed. Change `RSpec.#{name}` to `#{name}` or " \
-
"move this to a top-level scope."
-
end
-
-
1
thread_data[:in_example_group] = true
-
end
-
-
11
begin
-
-
11
description = args.shift
-
11
combined_metadata = metadata.dup
-
11
combined_metadata.merge!(args.pop) if args.last.is_a? Hash
-
11
args << combined_metadata
-
-
11
subclass(self, description, args, &example_group_block).tap do |child|
-
11
children << child
-
end
-
-
ensure
-
11
thread_data.delete(:in_example_group) if top_level
-
end
-
end
-
-
7
RSpec::Core::DSL.expose_example_group_alias(name)
-
end
-
-
1
define_example_group_method :example_group
-
-
# An alias of `example_group`. Generally used when grouping examples by a
-
# thing you are describing (e.g. an object, class or method).
-
# @see example_group
-
1
define_example_group_method :describe
-
-
# An alias of `example_group`. Generally used when grouping examples
-
# contextually (e.g. "with xyz", "when xyz" or "if xyz").
-
# @see example_group
-
1
define_example_group_method :context
-
-
# Shortcut to temporarily make an example group skipped.
-
# @see example_group
-
1
define_example_group_method :xdescribe, :skip => "Temporarily skipped with xdescribe"
-
-
# Shortcut to temporarily make an example group skipped.
-
# @see example_group
-
1
define_example_group_method :xcontext, :skip => "Temporarily skipped with xcontext"
-
-
# Shortcut to define an example group with `:focus => true`.
-
# @see example_group
-
1
define_example_group_method :fdescribe, :focus => true
-
-
# Shortcut to define an example group with `:focus => true`.
-
# @see example_group
-
1
define_example_group_method :fcontext, :focus => true
-
-
# @!endgroup
-
-
# @!group Including Shared Example Groups
-
-
# @private
-
# @macro [attach] define_nested_shared_group_method
-
# @!scope class
-
#
-
# @see SharedExampleGroup
-
1
def self.define_nested_shared_group_method(new_name, report_label="it should behave like")
-
2
idempotently_define_singleton_method(new_name) do |name, *args, &customization_block|
-
# Pass :caller so the :location metadata is set properly.
-
# Otherwise, it'll be set to the next line because that's
-
# the block's source_location.
-
group = example_group("#{report_label} #{name}", :caller => (the_caller = caller)) do
-
find_and_eval_shared("examples", name, the_caller.first, *args, &customization_block)
-
end
-
group.metadata[:shared_group_name] = name
-
group
-
end
-
end
-
-
# Generates a nested example group and includes the shared content
-
# mapped to `name` in the nested group.
-
1
define_nested_shared_group_method :it_behaves_like, "behaves like"
-
# Generates a nested example group and includes the shared content
-
# mapped to `name` in the nested group.
-
1
define_nested_shared_group_method :it_should_behave_like
-
-
# Includes shared content mapped to `name` directly in the group in which
-
# it is declared, as opposed to `it_behaves_like`, which creates a nested
-
# group. If given a block, that block is also eval'd in the current
-
# context.
-
#
-
# @see SharedExampleGroup
-
1
def self.include_context(name, *args, &block)
-
find_and_eval_shared("context", name, caller.first, *args, &block)
-
end
-
-
# Includes shared content mapped to `name` directly in the group in which
-
# it is declared, as opposed to `it_behaves_like`, which creates a nested
-
# group. If given a block, that block is also eval'd in the current
-
# context.
-
#
-
# @see SharedExampleGroup
-
1
def self.include_examples(name, *args, &block)
-
find_and_eval_shared("examples", name, caller.first, *args, &block)
-
end
-
-
# Clear memoized values when adding/removing examples
-
# @private
-
1
def self.reset_memoized
-
@descendant_filtered_examples = nil
-
@_descendants = nil
-
@parent_groups = nil
-
@declaration_locations = nil
-
end
-
-
# Adds an example to the example group
-
1
def self.add_example(example)
-
reset_memoized
-
examples << example
-
end
-
-
# Removes an example from the example group
-
1
def self.remove_example(example)
-
reset_memoized
-
examples.delete example
-
end
-
-
# @private
-
1
def self.find_and_eval_shared(label, name, inclusion_location, *args, &customization_block)
-
shared_block = RSpec.world.shared_example_group_registry.find(parent_groups, name)
-
-
unless shared_block
-
raise ArgumentError, "Could not find shared #{label} #{name.inspect}"
-
end
-
-
SharedExampleGroupInclusionStackFrame.with_frame(name, Metadata.relative_path(inclusion_location)) do
-
module_exec(*args, &shared_block)
-
module_exec(&customization_block) if customization_block
-
end
-
end
-
-
# @!endgroup
-
-
# @private
-
1
def self.subclass(parent, description, args, &example_group_block)
-
11
subclass = Class.new(parent)
-
11
subclass.set_it_up(description, *args, &example_group_block)
-
11
subclass.module_exec(&example_group_block) if example_group_block
-
-
# The LetDefinitions module must be included _after_ other modules
-
# to ensure that it takes precedence when there are name collisions.
-
# Thus, we delay including it until after the example group block
-
# has been eval'd.
-
11
MemoizedHelpers.define_helpers_on(subclass)
-
-
11
subclass
-
end
-
-
# @private
-
1
def self.set_it_up(description, *args, &example_group_block)
-
# Ruby 1.9 has a bug that can lead to infinite recursion and a
-
# SystemStackError if you include a module in a superclass after
-
# including it in a subclass: https://gist.github.com/845896
-
# To prevent this, we must include any modules in
-
# RSpec::Core::ExampleGroup before users create example groups and have
-
# a chance to include the same module in a subclass of
-
# RSpec::Core::ExampleGroup. So we need to configure example groups
-
# here.
-
11
ensure_example_groups_are_configured
-
-
11
user_metadata = Metadata.build_hash_from(args)
-
-
11
@metadata = Metadata::ExampleGroupHash.create(
-
superclass_metadata, user_metadata,
-
superclass.method(:next_runnable_index_for),
-
description, *args, &example_group_block
-
)
-
11
ExampleGroups.assign_const(self)
-
-
11
hooks.register_globals(self, RSpec.configuration.hooks)
-
11
RSpec.configuration.configure_group(self)
-
end
-
-
# @private
-
1
def self.examples
-
40
@examples ||= []
-
end
-
-
# @private
-
1
def self.filtered_examples
-
44
RSpec.world.filtered_examples[self]
-
end
-
-
# @private
-
1
def self.descendant_filtered_examples
-
@descendant_filtered_examples ||= filtered_examples +
-
43
FlatMap.flat_map(children, &:descendant_filtered_examples)
-
end
-
-
# @private
-
1
def self.children
-
64
@children ||= []
-
end
-
-
# @private
-
1
def self.next_runnable_index_for(file)
-
if self == ExampleGroup
-
1
RSpec.world.num_example_groups_defined_in(file)
-
else
-
20
children.count + examples.count
-
21
end + 1
-
end
-
-
# @private
-
1
def self.descendants
-
12
@_descendants ||= [self] + FlatMap.flat_map(children, &:descendants)
-
end
-
-
## @private
-
1
def self.parent_groups
-
419
@parent_groups ||= ancestors.select { |a| a < RSpec::Core::ExampleGroup }
-
end
-
-
# @private
-
1
def self.top_level?
-
superclass == ExampleGroup
-
end
-
-
# @private
-
1
def self.ensure_example_groups_are_configured
-
11
unless defined?(@@example_groups_configured)
-
1
RSpec.configuration.configure_mock_framework
-
1
RSpec.configuration.configure_expectation_framework
-
# rubocop:disable Style/ClassVars
-
1
@@example_groups_configured = true
-
# rubocop:enable Style/ClassVars
-
end
-
end
-
-
# @private
-
1
def self.before_context_ivars
-
89
@before_context_ivars ||= {}
-
end
-
-
# @private
-
1
def self.store_before_context_ivars(example_group_instance)
-
20
each_instance_variable_for_example(example_group_instance) do |ivar|
-
20
before_context_ivars[ivar] = example_group_instance.instance_variable_get(ivar)
-
end
-
end
-
-
# @private
-
1
def self.run_before_context_hooks(example_group_instance)
-
20
set_ivars(example_group_instance, superclass_before_context_ivars)
-
-
20
ContextHookMemoized::Before.isolate_for_context_hook(example_group_instance) do
-
20
hooks.run(:before, :context, example_group_instance)
-
end
-
ensure
-
20
store_before_context_ivars(example_group_instance)
-
end
-
-
1
if RUBY_VERSION.to_f >= 1.9
-
# @private
-
1
def self.superclass_before_context_ivars
-
20
superclass.before_context_ivars
-
end
-
else # 1.8.7
-
# :nocov:
-
skipped
# @private
-
skipped
def self.superclass_before_context_ivars
-
skipped
if superclass.respond_to?(:before_context_ivars)
-
skipped
superclass.before_context_ivars
-
skipped
else
-
skipped
# `self` must be the singleton class of an ExampleGroup instance.
-
skipped
# On 1.8.7, the superclass of a singleton class of an instance of A
-
skipped
# is A's singleton class. On 1.9+, it's A. On 1.8.7, the first ancestor
-
skipped
# is A, so we can mirror 1.8.7's behavior here. Note that we have to
-
skipped
# search for the first that responds to `before_context_ivars`
-
skipped
# in case a module has been included in the singleton class.
-
skipped
ancestors.find { |a| a.respond_to?(:before_context_ivars) }.before_context_ivars
-
skipped
end
-
skipped
end
-
# :nocov:
-
end
-
-
# @private
-
1
def self.run_after_context_hooks(example_group_instance)
-
20
set_ivars(example_group_instance, before_context_ivars)
-
-
20
ContextHookMemoized::After.isolate_for_context_hook(example_group_instance) do
-
20
hooks.run(:after, :context, example_group_instance)
-
end
-
ensure
-
20
before_context_ivars.clear
-
end
-
-
# Runs all the examples in this group.
-
1
def self.run(reporter=RSpec::Core::NullReporter)
-
11
return if RSpec.world.wants_to_quit
-
11
reporter.example_group_started(self)
-
-
11
should_run_context_hooks = descendant_filtered_examples.any?
-
11
begin
-
11
run_before_context_hooks(new('before(:context) hook')) if should_run_context_hooks
-
11
result_for_this_group = run_examples(reporter)
-
21
results_for_descendants = ordering_strategy.order(children).map { |child| child.run(reporter) }.all?
-
11
result_for_this_group && results_for_descendants
-
rescue Pending::SkipDeclaredInExample => ex
-
for_filtered_examples(reporter) { |example| example.skip_with_exception(reporter, ex) }
-
true
-
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => ex
-
for_filtered_examples(reporter) { |example| example.fail_with_exception(reporter, ex) }
-
RSpec.world.wants_to_quit = true if reporter.fail_fast_limit_met?
-
false
-
ensure
-
11
run_after_context_hooks(new('after(:context) hook')) if should_run_context_hooks
-
11
reporter.example_group_finished(self)
-
11
end
-
end
-
-
# @private
-
1
def self.ordering_strategy
-
22
order = metadata.fetch(:order, :global)
-
22
registry = RSpec.configuration.ordering_registry
-
-
22
registry.fetch(order) do
-
warn <<-WARNING.gsub(/^ +\|/, '')
-
|WARNING: Ignoring unknown ordering specified using `:order => #{order.inspect}` metadata.
-
| Falling back to configured global ordering.
-
| Unrecognized ordering specified at: #{location}
-
WARNING
-
-
registry.fetch(:global)
-
end
-
end
-
-
# @private
-
1
def self.run_examples(reporter)
-
ordering_strategy.order(filtered_examples).map do |example|
-
9
next if RSpec.world.wants_to_quit
-
9
instance = new(example.inspect_output)
-
9
set_ivars(instance, before_context_ivars)
-
9
succeeded = example.run(instance, reporter)
-
9
if !succeeded && reporter.fail_fast_limit_met?
-
RSpec.world.wants_to_quit = true
-
end
-
9
succeeded
-
11
end.all?
-
end
-
-
# @private
-
1
def self.for_filtered_examples(reporter, &block)
-
filtered_examples.each(&block)
-
-
children.each do |child|
-
reporter.example_group_started(child)
-
child.for_filtered_examples(reporter, &block)
-
reporter.example_group_finished(child)
-
end
-
false
-
end
-
-
# @private
-
1
def self.declaration_locations
-
@declaration_locations ||= [Metadata.location_tuple_from(metadata)] +
-
examples.map { |e| Metadata.location_tuple_from(e.metadata) } +
-
FlatMap.flat_map(children, &:declaration_locations)
-
end
-
-
# @return [String] the unique id of this example group. Pass
-
# this at the command line to re-run this exact example group.
-
1
def self.id
-
Metadata.id_from(metadata)
-
end
-
-
# @private
-
1
def self.top_level_description
-
parent_groups.last.description
-
end
-
-
# @private
-
1
def self.set_ivars(instance, ivars)
-
97
ivars.each { |name, value| instance.instance_variable_set(name, value) }
-
end
-
-
1
if RUBY_VERSION.to_f < 1.9
-
# :nocov:
-
skipped
# @private
-
skipped
INSTANCE_VARIABLE_TO_IGNORE = '@__inspect_output'.freeze
-
# :nocov:
-
else
-
# @private
-
1
INSTANCE_VARIABLE_TO_IGNORE = :@__inspect_output
-
end
-
-
# @private
-
1
def self.each_instance_variable_for_example(group)
-
20
group.instance_variables.each do |ivar|
-
40
yield ivar unless ivar == INSTANCE_VARIABLE_TO_IGNORE
-
end
-
end
-
-
1
def initialize(inspect_output=nil)
-
32
@__inspect_output = inspect_output || '(no description provided)'
-
32
super() # no args get passed
-
end
-
-
# @private
-
1
def inspect
-
"#<#{self.class} #{@__inspect_output}>"
-
end
-
-
1
unless method_defined?(:singleton_class) # for 1.8.7
-
# :nocov:
-
skipped
# @private
-
skipped
def singleton_class
-
skipped
class << self; self; end
-
skipped
end
-
# :nocov:
-
end
-
-
# Raised when an RSpec API is called in the wrong scope, such as `before`
-
# being called from within an example rather than from within an example
-
# group block.
-
1
WrongScopeError = Class.new(NoMethodError)
-
-
1
def self.method_missing(name, *args)
-
if method_defined?(name)
-
raise WrongScopeError,
-
"`#{name}` is not available on an example group (e.g. a " \
-
"`describe` or `context` block). It is only available from " \
-
"within individual examples (e.g. `it` blocks) or from " \
-
"constructs that run in the scope of an example (e.g. " \
-
"`before`, `let`, etc)."
-
end
-
-
super
-
end
-
1
private_class_method :method_missing
-
-
1
private
-
-
1
def method_missing(name, *args)
-
if self.class.respond_to?(name)
-
raise WrongScopeError,
-
"`#{name}` is not available from within an example (e.g. an " \
-
"`it` block) or from constructs that run in the scope of an " \
-
"example (e.g. `before`, `let`, etc). It is only available " \
-
"on an example group (e.g. a `describe` or `context` block)."
-
end
-
-
super
-
end
-
end
-
# rubocop:enable Metrics/ClassLength
-
-
# @private
-
# Unnamed example group used by `SuiteHookContext`.
-
1
class AnonymousExampleGroup < ExampleGroup
-
1
def self.metadata
-
1
{}
-
end
-
end
-
-
# Contains information about the inclusion site of a shared example group.
-
1
class SharedExampleGroupInclusionStackFrame
-
# @return [String] the name of the shared example group
-
1
attr_reader :shared_group_name
-
# @return [String] the location where the shared example was included
-
1
attr_reader :inclusion_location
-
-
1
def initialize(shared_group_name, inclusion_location)
-
@shared_group_name = shared_group_name
-
@inclusion_location = inclusion_location
-
end
-
-
# @return [String] The {#inclusion_location}, formatted for display by a formatter.
-
1
def formatted_inclusion_location
-
@formatted_inclusion_location ||= begin
-
RSpec.configuration.backtrace_formatter.backtrace_line(
-
inclusion_location.sub(/(:\d+):in .+$/, '\1')
-
)
-
end
-
end
-
-
# @return [String] Description of this stack frame, in the form used by
-
# RSpec's built-in formatters.
-
1
def description
-
@description ||= "Shared Example Group: #{shared_group_name.inspect} " \
-
"called from #{formatted_inclusion_location}"
-
end
-
-
# @private
-
1
def self.current_backtrace
-
10
shared_example_group_inclusions.reverse
-
end
-
-
# @private
-
1
def self.with_frame(name, location)
-
current_stack = shared_example_group_inclusions
-
current_stack << new(name, location)
-
yield
-
ensure
-
current_stack.pop
-
end
-
-
# @private
-
1
def self.shared_example_group_inclusions
-
10
RSpec::Support.thread_local_data[:shared_example_group_inclusions] ||= []
-
end
-
end
-
end
-
-
# @private
-
#
-
# Namespace for the example group subclasses generated by top-level
-
# `describe`.
-
1
module ExampleGroups
-
1
extend Support::RecursiveConstMethods
-
-
1
def self.assign_const(group)
-
11
base_name = base_name_for(group)
-
11
const_scope = constant_scope_for(group)
-
11
name = disambiguate(base_name, const_scope)
-
-
11
const_scope.const_set(name, group)
-
end
-
-
1
def self.constant_scope_for(group)
-
11
const_scope = group.superclass
-
11
const_scope = self if const_scope == ::RSpec::Core::ExampleGroup
-
11
const_scope
-
end
-
-
1
def self.base_name_for(group)
-
11
return "Anonymous" if group.description.empty?
-
-
# Convert to CamelCase.
-
11
name = ' ' << group.description
-
11
name.gsub!(/[^0-9a-zA-Z]+([0-9a-zA-Z])/) do
-
32
match = ::Regexp.last_match[1]
-
32
match.upcase!
-
32
match
-
end
-
-
11
name.lstrip! # Remove leading whitespace
-
11
name.gsub!(/\W/, ''.freeze) # JRuby, RBX and others don't like non-ascii in const names
-
-
# Ruby requires first const letter to be A-Z. Use `Nested`
-
# as necessary to enforce that.
-
11
name.gsub!(/\A([^A-Z]|\z)/, 'Nested\1'.freeze)
-
-
11
name
-
end
-
-
1
if RUBY_VERSION == '1.9.2'
-
# :nocov:
-
skipped
class << self
-
skipped
alias _base_name_for base_name_for
-
skipped
def base_name_for(group)
-
skipped
_base_name_for(group) + '_'
-
skipped
end
-
skipped
end
-
skipped
private_class_method :_base_name_for
-
# :nocov:
-
end
-
-
1
def self.disambiguate(name, const_scope)
-
11
return name unless const_defined_on?(const_scope, name)
-
-
# Add a trailing number if needed to disambiguate from an existing
-
# constant.
-
name << "_2"
-
name.next! while const_defined_on?(const_scope, name)
-
name
-
end
-
end
-
end
-
1
module RSpec
-
1
module Core
-
# @private
-
1
class FilterManager
-
1
attr_reader :exclusions, :inclusions
-
-
1
def initialize
-
2
@exclusions, @inclusions = FilterRules.build
-
end
-
-
# @api private
-
#
-
# @param file_path [String]
-
# @param line_numbers [Array]
-
1
def add_location(file_path, line_numbers)
-
# locations is a hash of expanded paths to arrays of line
-
# numbers to match against. e.g.
-
# { "path/to/file.rb" => [37, 42] }
-
add_path_to_arrays_filter(:locations, File.expand_path(file_path), line_numbers)
-
end
-
-
1
def add_ids(rerun_path, scoped_ids)
-
# ids is a hash of relative paths to arrays of ids
-
# to match against. e.g.
-
# { "./path/to/file.rb" => ["1:1", "2:4"] }
-
rerun_path = Metadata.relative_path(File.expand_path rerun_path)
-
add_path_to_arrays_filter(:ids, rerun_path, scoped_ids)
-
end
-
-
1
def empty?
-
1
inclusions.empty? && exclusions.empty?
-
end
-
-
1
def prune(examples)
-
# Semantically, this is unnecessary (the filtering below will return the empty
-
# array unmodified), but for perf reasons it's worth exiting early here. Users
-
# commonly have top-level examples groups that do not have any direct examples
-
# and instead have nested groups with examples. In that kind of situation,
-
# `examples` will be empty.
-
11
return examples if examples.empty?
-
-
8
examples = prune_conditionally_filtered_examples(examples)
-
-
8
if inclusions.standalone?
-
examples.select { |e| inclusions.include_example?(e) }
-
else
-
8
locations, ids, non_scoped_inclusions = inclusions.split_file_scoped_rules
-
-
8
examples.select do |ex|
-
9
file_scoped_include?(ex.metadata, ids, locations) do
-
9
!exclusions.include_example?(ex) && non_scoped_inclusions.include_example?(ex)
-
end
-
end
-
end
-
end
-
-
1
def exclude(*args)
-
exclusions.add(args.last)
-
end
-
-
1
def exclude_only(*args)
-
exclusions.use_only(args.last)
-
end
-
-
1
def exclude_with_low_priority(*args)
-
exclusions.add_with_low_priority(args.last)
-
end
-
-
1
def include(*args)
-
inclusions.add(args.last)
-
end
-
-
1
def include_only(*args)
-
inclusions.use_only(args.last)
-
end
-
-
1
def include_with_low_priority(*args)
-
inclusions.add_with_low_priority(args.last)
-
end
-
-
1
private
-
-
1
def add_path_to_arrays_filter(filter_key, path, values)
-
filter = inclusions.delete(filter_key) || Hash.new { |h, k| h[k] = [] }
-
filter[path].concat(values)
-
inclusions.add(filter_key => filter)
-
end
-
-
1
def prune_conditionally_filtered_examples(examples)
-
8
examples.reject do |ex|
-
9
meta = ex.metadata
-
9
!meta.fetch(:if, true) || meta[:unless]
-
end
-
end
-
-
# When a user specifies a particular spec location, that takes priority
-
# over any exclusion filters (such as if the spec is tagged with `:slow`
-
# and there is a `:slow => true` exclusion filter), but only for specs
-
# defined in the same file as the location filters. Excluded specs in
-
# other files should still be excluded.
-
1
def file_scoped_include?(ex_metadata, ids, locations)
-
9
no_id_filters = ids[ex_metadata[:rerun_file_path]].empty?
-
9
no_location_filters = locations[
-
File.expand_path(ex_metadata[:rerun_file_path])
-
].empty?
-
-
9
return yield if no_location_filters && no_id_filters
-
-
MetadataFilter.filter_applies?(:ids, ids, ex_metadata) ||
-
MetadataFilter.filter_applies?(:locations, locations, ex_metadata)
-
end
-
end
-
-
# @private
-
1
class FilterRules
-
1
PROC_HEX_NUMBER = /0x[0-9a-f]+@/
-
1
PROJECT_DIR = File.expand_path('.')
-
-
1
attr_accessor :opposite
-
1
attr_reader :rules
-
-
1
def self.build
-
2
exclusions = ExclusionRules.new
-
2
inclusions = InclusionRules.new
-
2
exclusions.opposite = inclusions
-
2
inclusions.opposite = exclusions
-
2
[exclusions, inclusions]
-
end
-
-
1
def initialize(rules={})
-
12
@rules = rules
-
end
-
-
1
def add(updated)
-
@rules.merge!(updated).each_key { |k| opposite.delete(k) }
-
end
-
-
1
def add_with_low_priority(updated)
-
updated = updated.merge(@rules)
-
opposite.each_pair { |k, v| updated.delete(k) if updated[k] == v }
-
@rules.replace(updated)
-
end
-
-
1
def use_only(updated)
-
updated.each_key { |k| opposite.delete(k) }
-
@rules.replace(updated)
-
end
-
-
1
def clear
-
@rules.clear
-
end
-
-
1
def delete(key)
-
@rules.delete(key)
-
end
-
-
1
def fetch(*args, &block)
-
@rules.fetch(*args, &block)
-
end
-
-
1
def [](key)
-
@rules[key]
-
end
-
-
1
def empty?
-
4
rules.empty?
-
end
-
-
1
def each_pair(&block)
-
@rules.each_pair(&block)
-
end
-
-
1
def description
-
rules.inspect.gsub(PROC_HEX_NUMBER, '').gsub(PROJECT_DIR, '.').gsub(' (lambda)', '')
-
end
-
-
1
def include_example?(example)
-
9
MetadataFilter.apply?(:any?, @rules, example.metadata)
-
end
-
end
-
-
# @private
-
1
ExclusionRules = FilterRules
-
-
# @private
-
1
class InclusionRules < FilterRules
-
1
def add(*args)
-
apply_standalone_filter(*args) || super
-
end
-
-
1
def add_with_low_priority(*args)
-
apply_standalone_filter(*args) || super
-
end
-
-
1
def include_example?(example)
-
9
@rules.empty? || super
-
end
-
-
1
def standalone?
-
8
is_standalone_filter?(@rules)
-
end
-
-
1
def split_file_scoped_rules
-
8
rules_dup = @rules.dup
-
16
locations = rules_dup.delete(:locations) { Hash.new([]) }
-
16
ids = rules_dup.delete(:ids) { Hash.new([]) }
-
-
8
return locations, ids, self.class.new(rules_dup)
-
end
-
-
1
private
-
-
1
def apply_standalone_filter(updated)
-
return true if standalone?
-
return nil unless is_standalone_filter?(updated)
-
-
replace_filters(updated)
-
true
-
end
-
-
1
def replace_filters(new_rules)
-
@rules.replace(new_rules)
-
opposite.clear
-
end
-
-
1
def is_standalone_filter?(rules)
-
8
rules.key?(:full_description)
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Core
-
# @private
-
1
module FlatMap
-
1
if [].respond_to?(:flat_map)
-
1
def flat_map(array, &block)
-
47
array.flat_map(&block)
-
end
-
else # for 1.8.7
-
# :nocov:
-
skipped
def flat_map(array, &block)
-
skipped
array.map(&block).flatten(1)
-
skipped
end
-
# :nocov:
-
end
-
-
1
module_function :flat_map
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support "directory_maker"
-
# ## Built-in Formatters
-
#
-
# * progress (default) - Prints dots for passing examples, `F` for failures, `*`
-
# for pending.
-
# * documentation - Prints the docstrings passed to `describe` and `it` methods
-
# (and their aliases).
-
# * html
-
# * json - Useful for archiving data for subsequent analysis.
-
#
-
# The progress formatter is the default, but you can choose any one or more of
-
# the other formatters by passing with the `--format` (or `-f` for short)
-
# command-line option, e.g.
-
#
-
# rspec --format documentation
-
#
-
# You can also send the output of multiple formatters to different streams, e.g.
-
#
-
# rspec --format documentation --format html --out results.html
-
#
-
# This example sends the output of the documentation formatter to `$stdout`, and
-
# the output of the html formatter to results.html.
-
#
-
# ## Custom Formatters
-
#
-
# You can tell RSpec to use a custom formatter by passing its path and name to
-
# the `rspec` commmand. For example, if you define MyCustomFormatter in
-
# path/to/my_custom_formatter.rb, you would type this command:
-
#
-
# rspec --require path/to/my_custom_formatter.rb --format MyCustomFormatter
-
#
-
# The reporter calls every formatter with this protocol:
-
#
-
# * To start
-
# * `start(StartNotification)`
-
# * Once per example group
-
# * `example_group_started(GroupNotification)`
-
# * Once per example
-
# * `example_started(ExampleNotification)`
-
# * One of these per example, depending on outcome
-
# * `example_passed(ExampleNotification)`
-
# * `example_failed(FailedExampleNotification)`
-
# * `example_pending(ExampleNotification)`
-
# * Optionally at any time
-
# * `message(MessageNotification)`
-
# * At the end of the suite
-
# * `stop(ExamplesNotification)`
-
# * `start_dump(NullNotification)`
-
# * `dump_pending(ExamplesNotification)`
-
# * `dump_failures(ExamplesNotification)`
-
# * `dump_summary(SummaryNotification)`
-
# * `seed(SeedNotification)`
-
# * `close(NullNotification)`
-
#
-
# Only the notifications to which you subscribe your formatter will be called
-
# on your formatter. To subscribe your formatter use:
-
# `RSpec::Core::Formatters#register` e.g.
-
#
-
# `RSpec::Core::Formatters.register FormatterClassName, :example_passed, :example_failed`
-
#
-
# We recommend you implement the methods yourself; for simplicity we provide the
-
# default formatter output via our notification objects but if you prefer you
-
# can subclass `RSpec::Core::Formatters::BaseTextFormatter` and override the
-
# methods you wish to enhance.
-
#
-
# @see RSpec::Core::Formatters::BaseTextFormatter
-
# @see RSpec::Core::Reporter
-
1
module RSpec::Core::Formatters
-
1
autoload :DocumentationFormatter, 'rspec/core/formatters/documentation_formatter'
-
1
autoload :HtmlFormatter, 'rspec/core/formatters/html_formatter'
-
1
autoload :FallbackMessageFormatter, 'rspec/core/formatters/fallback_message_formatter'
-
1
autoload :ProgressFormatter, 'rspec/core/formatters/progress_formatter'
-
1
autoload :ProfileFormatter, 'rspec/core/formatters/profile_formatter'
-
1
autoload :JsonFormatter, 'rspec/core/formatters/json_formatter'
-
1
autoload :BisectFormatter, 'rspec/core/formatters/bisect_formatter'
-
1
autoload :ExceptionPresenter, 'rspec/core/formatters/exception_presenter'
-
-
# Register the formatter class
-
# @param formatter_class [Class] formatter class to register
-
# @param notifications [Symbol, ...] one or more notifications to be
-
# registered to the specified formatter
-
#
-
# @see RSpec::Core::Formatters::BaseFormatter
-
1
def self.register(formatter_class, *notifications)
-
4
Loader.formatters[formatter_class] = notifications
-
end
-
-
# @api private
-
#
-
# `RSpec::Core::Formatters::Loader` is an internal class for
-
# managing formatters used by a particular configuration. It is
-
# not expected to be used directly, but only through the configuration
-
# interface.
-
1
class Loader
-
# @api private
-
#
-
# Internal formatters are stored here when loaded.
-
1
def self.formatters
-
29
@formatters ||= {}
-
end
-
-
# @api private
-
1
def initialize(reporter)
-
1
@formatters = []
-
1
@reporter = reporter
-
1
self.default_formatter = 'progress'
-
end
-
-
# @return [Array] the loaded formatters
-
1
attr_reader :formatters
-
-
# @return [Reporter] the reporter
-
1
attr_reader :reporter
-
-
# @return [String] the default formatter to setup, defaults to `progress`
-
1
attr_accessor :default_formatter
-
-
# @private
-
1
def setup_default(output_stream, deprecation_stream)
-
1
add default_formatter, output_stream if @formatters.empty?
-
-
2
unless @formatters.any? { |formatter| DeprecationFormatter === formatter }
-
1
add DeprecationFormatter, deprecation_stream, output_stream
-
end
-
-
1
unless existing_formatter_implements?(:message)
-
1
add FallbackMessageFormatter, output_stream
-
end
-
-
1
return unless RSpec.configuration.profile_examples?
-
-
@reporter.setup_profiler
-
-
return if existing_formatter_implements?(:dump_profile)
-
-
add RSpec::Core::Formatters::ProfileFormatter, output_stream
-
end
-
-
# @private
-
1
def add(formatter_to_use, *paths)
-
3
formatter_class = find_formatter(formatter_to_use)
-
-
7
args = paths.map { |p| p.respond_to?(:puts) ? p : file_at(p) }
-
-
3
if !Loader.formatters[formatter_class].nil?
-
3
formatter = formatter_class.new(*args)
-
3
register formatter, notifications_for(formatter_class)
-
elsif defined?(RSpec::LegacyFormatters)
-
formatter = RSpec::LegacyFormatters.load_formatter formatter_class, *args
-
register formatter, formatter.notifications
-
else
-
call_site = "Formatter added at: #{::RSpec::CallerFilter.first_non_rspec_line}"
-
-
RSpec.warn_deprecation <<-WARNING.gsub(/\s*\|/, ' ')
-
|The #{formatter_class} formatter uses the deprecated formatter
-
|interface not supported directly by RSpec 3.
-
|
-
|To continue to use this formatter you must install the
-
|`rspec-legacy_formatters` gem, which provides support
-
|for legacy formatters or upgrade the formatter to a
-
|compatible version.
-
|
-
|#{call_site}
-
WARNING
-
end
-
end
-
-
1
private
-
-
1
def find_formatter(formatter_to_use)
-
built_in_formatter(formatter_to_use) ||
-
3
custom_formatter(formatter_to_use) ||
-
(raise ArgumentError, "Formatter '#{formatter_to_use}' unknown - " \
-
"maybe you meant 'documentation' or 'progress'?.")
-
end
-
-
1
def register(formatter, notifications)
-
3
return if duplicate_formatter_exists?(formatter)
-
3
@reporter.register_listener formatter, *notifications
-
3
@formatters << formatter
-
3
formatter
-
end
-
-
1
def duplicate_formatter_exists?(new_formatter)
-
3
@formatters.any? do |formatter|
-
3
formatter.class == new_formatter.class && formatter.output == new_formatter.output
-
end
-
end
-
-
1
def existing_formatter_implements?(notification)
-
1
@reporter.registered_listeners(notification).any?
-
end
-
-
1
def built_in_formatter(key)
-
3
case key.to_s
-
when 'd', 'doc', 'documentation'
-
DocumentationFormatter
-
when 'h', 'html'
-
HtmlFormatter
-
when 'p', 'progress'
-
ProgressFormatter
-
when 'j', 'json'
-
JsonFormatter
-
when 'bisect'
-
BisectFormatter
-
end
-
end
-
-
1
def notifications_for(formatter_class)
-
3
formatter_class.ancestors.inject(::RSpec::Core::Set.new) do |notifications, klass|
-
40
notifications.merge Loader.formatters.fetch(klass) { ::RSpec::Core::Set.new }
-
end
-
end
-
-
1
def custom_formatter(formatter_ref)
-
3
if Class === formatter_ref
-
2
formatter_ref
-
1
elsif string_const?(formatter_ref)
-
1
begin
-
5
formatter_ref.gsub(/^::/, '').split('::').inject(Object) { |a, e| a.const_get e }
-
rescue NameError
-
require(path_for(formatter_ref)) ? retry : raise
-
end
-
end
-
end
-
-
1
def string_const?(str)
-
1
str.is_a?(String) && /\A[A-Z][a-zA-Z0-9_:]*\z/ =~ str
-
end
-
-
1
def path_for(const_ref)
-
underscore_with_fix_for_non_standard_rspec_naming(const_ref)
-
end
-
-
1
def underscore_with_fix_for_non_standard_rspec_naming(string)
-
underscore(string).sub(%r{(^|/)r_spec($|/)}, '\\1rspec\\2')
-
end
-
-
# activesupport/lib/active_support/inflector/methods.rb, line 48
-
1
def underscore(camel_cased_word)
-
word = camel_cased_word.to_s.dup
-
word.gsub!(/::/, '/')
-
word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
-
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
-
word.tr!("-", "_")
-
word.downcase!
-
word
-
end
-
-
1
def file_at(path)
-
RSpec::Support::DirectoryMaker.mkdir_p(File.dirname(path))
-
File.new(path, 'w')
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_core "formatters/helpers"
-
1
require 'stringio'
-
-
1
module RSpec
-
1
module Core
-
1
module Formatters
-
# RSpec's built-in formatters are all subclasses of
-
# RSpec::Core::Formatters::BaseTextFormatter.
-
#
-
# @see RSpec::Core::Formatters::BaseTextFormatter
-
# @see RSpec::Core::Reporter
-
# @see RSpec::Core::Formatters::Protocol
-
1
class BaseFormatter
-
# All formatters inheriting from this formatter will receive these
-
# notifications.
-
1
Formatters.register self, :start, :example_group_started, :close
-
1
attr_accessor :example_group
-
1
attr_reader :output
-
-
# @api public
-
# @param output [IO] the formatter output
-
# @see RSpec::Core::Formatters::Protocol#initialize
-
1
def initialize(output)
-
1
@output = output || StringIO.new
-
1
@example_group = nil
-
end
-
-
# @api public
-
#
-
# @param notification [StartNotification]
-
# @see RSpec::Core::Formatters::Protocol#start
-
1
def start(notification)
-
1
start_sync_output
-
1
@example_count = notification.count
-
end
-
-
# @api public
-
#
-
# @param notification [GroupNotification] containing example_group
-
# subclass of `RSpec::Core::ExampleGroup`
-
# @see RSpec::Core::Formatters::Protocol#example_group_started
-
1
def example_group_started(notification)
-
11
@example_group = notification.group
-
end
-
-
# @api public
-
#
-
# @param _notification [NullNotification] (Ignored)
-
# @see RSpec::Core::Formatters::Protocol#close
-
1
def close(_notification)
-
restore_sync_output
-
end
-
-
1
private
-
-
1
def start_sync_output
-
1
@old_sync, output.sync = output.sync, true if output_supports_sync
-
end
-
-
1
def restore_sync_output
-
output.sync = @old_sync if output_supports_sync && !output.closed?
-
end
-
-
1
def output_supports_sync
-
1
output.respond_to?(:sync=)
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_core "formatters/helpers"
-
-
1
module RSpec
-
1
module Core
-
1
module Formatters
-
# @private
-
1
class DeprecationFormatter
-
1
Formatters.register self, :deprecation, :deprecation_summary
-
-
1
attr_reader :count, :deprecation_stream, :summary_stream
-
-
1
def initialize(deprecation_stream, summary_stream)
-
1
@deprecation_stream = deprecation_stream
-
1
@summary_stream = summary_stream
-
1
@seen_deprecations = Set.new
-
1
@count = 0
-
end
-
1
alias :output :deprecation_stream
-
-
1
def printer
-
@printer ||= case deprecation_stream
-
when File
-
ImmediatePrinter.new(FileStream.new(deprecation_stream),
-
summary_stream, self)
-
when RaiseErrorStream
-
ImmediatePrinter.new(deprecation_stream, summary_stream, self)
-
else
-
1
DelayedPrinter.new(deprecation_stream, summary_stream, self)
-
1
end
-
end
-
-
1
def deprecation(notification)
-
return if @seen_deprecations.include? notification
-
-
@count += 1
-
printer.print_deprecation_message notification
-
@seen_deprecations << notification
-
end
-
-
1
def deprecation_summary(_notification)
-
1
printer.deprecation_summary
-
end
-
-
1
def deprecation_message_for(data)
-
if data.message
-
SpecifiedDeprecationMessage.new(data)
-
else
-
GeneratedDeprecationMessage.new(data)
-
end
-
end
-
-
1
RAISE_ERROR_CONFIG_NOTICE = <<-EOS.gsub(/^\s+\|/, '')
-
|
-
|If you need more of the backtrace for any of these deprecations to
-
|identify where to make the necessary changes, you can configure
-
|`config.raise_errors_for_deprecations!`, and it will turn the
-
|deprecation warnings into errors, giving you the full backtrace.
-
EOS
-
-
1
DEPRECATION_STREAM_NOTICE = "Pass `--deprecation-out` or set " \
-
"`config.deprecation_stream` to a file for full output."
-
-
1
SpecifiedDeprecationMessage = Struct.new(:type) do
-
1
def initialize(data)
-
@message = data.message
-
super deprecation_type_for(data)
-
end
-
-
1
def to_s
-
output_formatted @message
-
end
-
-
1
def too_many_warnings_message
-
msg = "Too many similar deprecation messages reported, disregarding further reports. "
-
msg << DEPRECATION_STREAM_NOTICE
-
msg
-
end
-
-
1
private
-
-
1
def output_formatted(str)
-
return str unless str.lines.count > 1
-
separator = "#{'-' * 80}"
-
"#{separator}\n#{str.chomp}\n#{separator}"
-
end
-
-
1
def deprecation_type_for(data)
-
data.message.gsub(/(\w+\/)+\w+\.rb:\d+/, '')
-
end
-
end
-
-
1
GeneratedDeprecationMessage = Struct.new(:type) do
-
1
def initialize(data)
-
@data = data
-
super data.deprecated
-
end
-
-
1
def to_s
-
msg = "#{@data.deprecated} is deprecated."
-
msg << " Use #{@data.replacement} instead." if @data.replacement
-
msg << " Called from #{@data.call_site}." if @data.call_site
-
msg
-
end
-
-
1
def too_many_warnings_message
-
msg = "Too many uses of deprecated '#{type}'. "
-
msg << DEPRECATION_STREAM_NOTICE
-
msg
-
end
-
end
-
-
# @private
-
1
class ImmediatePrinter
-
1
attr_reader :deprecation_stream, :summary_stream, :deprecation_formatter
-
-
1
def initialize(deprecation_stream, summary_stream, deprecation_formatter)
-
@deprecation_stream = deprecation_stream
-
-
@summary_stream = summary_stream
-
@deprecation_formatter = deprecation_formatter
-
end
-
-
1
def print_deprecation_message(data)
-
deprecation_message = deprecation_formatter.deprecation_message_for(data)
-
deprecation_stream.puts deprecation_message.to_s
-
end
-
-
1
def deprecation_summary
-
return if deprecation_formatter.count.zero?
-
deprecation_stream.summarize(summary_stream, deprecation_formatter.count)
-
end
-
end
-
-
# @private
-
1
class DelayedPrinter
-
1
TOO_MANY_USES_LIMIT = 4
-
-
1
attr_reader :deprecation_stream, :summary_stream, :deprecation_formatter
-
-
1
def initialize(deprecation_stream, summary_stream, deprecation_formatter)
-
1
@deprecation_stream = deprecation_stream
-
1
@summary_stream = summary_stream
-
1
@deprecation_formatter = deprecation_formatter
-
1
@seen_deprecations = Hash.new { 0 }
-
1
@deprecation_messages = Hash.new { |h, k| h[k] = [] }
-
end
-
-
1
def print_deprecation_message(data)
-
deprecation_message = deprecation_formatter.deprecation_message_for(data)
-
@seen_deprecations[deprecation_message] += 1
-
-
stash_deprecation_message(deprecation_message)
-
end
-
-
1
def stash_deprecation_message(deprecation_message)
-
if @seen_deprecations[deprecation_message] < TOO_MANY_USES_LIMIT
-
@deprecation_messages[deprecation_message] << deprecation_message.to_s
-
elsif @seen_deprecations[deprecation_message] == TOO_MANY_USES_LIMIT
-
@deprecation_messages[deprecation_message] << deprecation_message.too_many_warnings_message
-
end
-
end
-
-
1
def deprecation_summary
-
1
return unless @deprecation_messages.any?
-
-
print_deferred_deprecation_warnings
-
deprecation_stream.puts RAISE_ERROR_CONFIG_NOTICE
-
-
summary_stream.puts "\n#{Helpers.pluralize(deprecation_formatter.count, 'deprecation warning')} total"
-
end
-
-
1
def print_deferred_deprecation_warnings
-
deprecation_stream.puts "\nDeprecation Warnings:\n\n"
-
@deprecation_messages.keys.sort_by(&:type).each do |deprecation|
-
messages = @deprecation_messages[deprecation]
-
messages.each { |msg| deprecation_stream.puts msg }
-
deprecation_stream.puts
-
end
-
end
-
end
-
-
# @private
-
# Not really a stream, but is usable in place of one.
-
1
class RaiseErrorStream
-
1
def puts(message)
-
raise DeprecationError, message
-
end
-
-
1
def summarize(summary_stream, deprecation_count)
-
summary_stream.puts "\n#{Helpers.pluralize(deprecation_count, 'deprecation')} found."
-
end
-
end
-
-
# @private
-
# Wraps a File object and provides file-specific operations.
-
1
class FileStream
-
1
def initialize(file)
-
@file = file
-
-
# In one of my test suites, I got lots of duplicate output in the
-
# deprecation file (e.g. 200 of the same deprecation, even though
-
# the `puts` below was only called 6 times). Setting `sync = true`
-
# fixes this (but we really have no idea why!).
-
@file.sync = true
-
end
-
-
1
def puts(*args)
-
@file.puts(*args)
-
end
-
-
1
def summarize(summary_stream, deprecation_count)
-
path = @file.respond_to?(:path) ? @file.path : @file.inspect
-
summary_stream.puts "\n#{Helpers.pluralize(deprecation_count, 'deprecation')} logged to #{path}"
-
puts RAISE_ERROR_CONFIG_NOTICE
-
end
-
end
-
end
-
end
-
-
# Deprecation Error.
-
1
DeprecationError = Class.new(StandardError)
-
end
-
end
-
# encoding: utf-8
-
1
RSpec::Support.require_rspec_core "formatters/snippet_extractor"
-
1
RSpec::Support.require_rspec_support "encoded_string"
-
-
1
module RSpec
-
1
module Core
-
1
module Formatters
-
# @private
-
1
class ExceptionPresenter
-
1
attr_reader :exception, :example, :description, :message_color,
-
:detail_formatter, :extra_detail_formatter, :backtrace_formatter
-
1
private :message_color, :detail_formatter, :extra_detail_formatter, :backtrace_formatter
-
-
1
def initialize(exception, example, options={})
-
@exception = exception
-
@example = example
-
@message_color = options.fetch(:message_color) { RSpec.configuration.failure_color }
-
@description = options.fetch(:description) { example.full_description }
-
@detail_formatter = options.fetch(:detail_formatter) { Proc.new {} }
-
@extra_detail_formatter = options.fetch(:extra_detail_formatter) { Proc.new {} }
-
@backtrace_formatter = options.fetch(:backtrace_formatter) { RSpec.configuration.backtrace_formatter }
-
@indentation = options.fetch(:indentation, 2)
-
@skip_shared_group_trace = options.fetch(:skip_shared_group_trace, false)
-
@failure_lines = options[:failure_lines]
-
end
-
-
1
def message_lines
-
add_shared_group_lines(failure_lines, Notifications::NullColorizer)
-
end
-
-
1
def colorized_message_lines(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
-
add_shared_group_lines(failure_lines, colorizer).map do |line|
-
colorizer.wrap line, message_color
-
end
-
end
-
-
1
def formatted_backtrace(exception=@exception)
-
backtrace_formatter.format_backtrace(exception.backtrace, example.metadata) +
-
formatted_cause(exception)
-
end
-
-
1
if RSpec::Support::RubyFeatures.supports_exception_cause?
-
1
def formatted_cause(exception)
-
last_cause = final_exception(exception)
-
cause = []
-
-
if exception.cause
-
cause << '------------------'
-
cause << '--- Caused by: ---'
-
cause << "#{exception_class_name(last_cause)}:" unless exception_class_name(last_cause) =~ /RSpec/
-
-
encoded_string(last_cause.message.to_s).split("\n").each do |line|
-
cause << " #{line}"
-
end
-
-
cause << (" #{backtrace_formatter.format_backtrace(last_cause.backtrace, example.metadata).first}")
-
end
-
-
cause
-
end
-
else
-
# :nocov:
-
skipped
def formatted_cause(_)
-
skipped
[]
-
skipped
end
-
# :nocov:
-
end
-
-
1
def colorized_formatted_backtrace(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
-
formatted_backtrace.map do |backtrace_info|
-
colorizer.wrap "# #{backtrace_info}", RSpec.configuration.detail_color
-
end
-
end
-
-
1
def fully_formatted(failure_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes)
-
lines = fully_formatted_lines(failure_number, colorizer)
-
lines.join("\n") << "\n"
-
end
-
-
1
def fully_formatted_lines(failure_number, colorizer)
-
lines = [
-
description,
-
detail_formatter.call(example, colorizer),
-
formatted_message_and_backtrace(colorizer),
-
extra_detail_formatter.call(failure_number, colorizer),
-
].compact.flatten
-
-
lines = indent_lines(lines, failure_number)
-
lines.unshift("")
-
lines
-
end
-
-
1
private
-
-
1
def final_exception(exception, previous=[])
-
cause = exception.cause
-
if cause && !previous.include?(cause)
-
previous << cause
-
final_exception(cause, previous)
-
else
-
exception
-
end
-
end
-
-
1
if String.method_defined?(:encoding)
-
1
def encoding_of(string)
-
string.encoding
-
end
-
-
1
def encoded_string(string)
-
RSpec::Support::EncodedString.new(string, Encoding.default_external)
-
end
-
else # for 1.8.7
-
# :nocov:
-
skipped
def encoding_of(_string)
-
skipped
end
-
skipped
-
skipped
def encoded_string(string)
-
skipped
RSpec::Support::EncodedString.new(string)
-
skipped
end
-
# :nocov:
-
end
-
-
1
def indent_lines(lines, failure_number)
-
alignment_basis = "#{' ' * @indentation}#{failure_number}) "
-
indentation = ' ' * alignment_basis.length
-
-
lines.each_with_index.map do |line, index|
-
if index == 0
-
"#{alignment_basis}#{line}"
-
elsif line.empty?
-
line
-
else
-
"#{indentation}#{line}"
-
end
-
end
-
end
-
-
1
def exception_class_name(exception=@exception)
-
name = exception.class.name.to_s
-
name = "(anonymous error class)" if name == ''
-
name
-
end
-
-
1
def failure_lines
-
@failure_lines ||= [].tap do |lines|
-
lines.concat(failure_slash_error_lines)
-
-
sections = [failure_slash_error_lines, exception_lines]
-
if sections.any? { |section| section.size > 1 } && !exception_lines.first.empty?
-
lines << ''
-
end
-
-
lines.concat(exception_lines)
-
lines.concat(extra_failure_lines)
-
end
-
end
-
-
1
def failure_slash_error_lines
-
lines = read_failed_lines
-
if lines.count == 1
-
lines[0] = "Failure/Error: #{lines[0].strip}"
-
else
-
least_indentation = SnippetExtractor.least_indentation_from(lines)
-
lines = lines.map { |line| line.sub(/^#{least_indentation}/, ' ') }
-
lines.unshift('Failure/Error:')
-
end
-
lines
-
end
-
-
1
def exception_lines
-
lines = []
-
lines << "#{exception_class_name}:" unless exception_class_name =~ /RSpec/
-
encoded_string(exception.message.to_s).split("\n").each do |line|
-
lines << (line.empty? ? line : " #{line}")
-
end
-
lines
-
end
-
-
1
def extra_failure_lines
-
@extra_failure_lines ||= begin
-
lines = Array(example.metadata[:extra_failure_lines])
-
unless lines.empty?
-
lines.unshift('')
-
lines.push('')
-
end
-
lines
-
end
-
end
-
-
1
def add_shared_group_lines(lines, colorizer)
-
return lines if @skip_shared_group_trace
-
-
example.metadata[:shared_group_inclusion_backtrace].each do |frame|
-
lines << colorizer.wrap(frame.description, RSpec.configuration.default_color)
-
end
-
-
lines
-
end
-
-
1
def read_failed_lines
-
matching_line = find_failed_line
-
unless matching_line
-
return ["Unable to find matching line from backtrace"]
-
end
-
-
file_path, line_number = matching_line.match(/(.+?):(\d+)(|:\d+)/)[1..2]
-
max_line_count = RSpec.configuration.max_displayed_failure_line_count
-
lines = SnippetExtractor.extract_expression_lines_at(file_path, line_number.to_i, max_line_count)
-
RSpec.world.source_cache.syntax_highlighter.highlight(lines)
-
rescue SnippetExtractor::NoSuchFileError
-
["Unable to find #{file_path} to read failed line"]
-
rescue SnippetExtractor::NoSuchLineError
-
["Unable to find matching line in #{file_path}"]
-
rescue SecurityError
-
["Unable to read failed line"]
-
end
-
-
1
def find_failed_line
-
line_regex = RSpec.configuration.in_project_source_dir_regex
-
loaded_spec_files = RSpec.configuration.loaded_spec_files
-
-
exception_backtrace.find do |line|
-
next unless (line_path = line[/(.+?):(\d+)(|:\d+)/, 1])
-
path = File.expand_path(line_path)
-
loaded_spec_files.include?(path) || path =~ line_regex
-
end || exception_backtrace.first
-
end
-
-
1
def formatted_message_and_backtrace(colorizer)
-
lines = colorized_message_lines(colorizer) + colorized_formatted_backtrace(colorizer)
-
encoding = encoding_of("")
-
lines.map do |line|
-
RSpec::Support::EncodedString.new(line, encoding)
-
end
-
end
-
-
1
def exception_backtrace
-
exception.backtrace || []
-
end
-
-
# @private
-
# Configuring the `ExceptionPresenter` with the right set of options to handle
-
# pending vs failed vs skipped and aggregated (or not) failures is not simple.
-
# This class takes care of building an appropriate `ExceptionPresenter` for the
-
# provided example.
-
1
class Factory
-
1
def build
-
ExceptionPresenter.new(@exception, @example, options)
-
end
-
-
1
private
-
-
1
def initialize(example)
-
@example = example
-
@execution_result = example.execution_result
-
@exception = if @execution_result.status == :pending
-
@execution_result.pending_exception
-
else
-
@execution_result.exception
-
end
-
end
-
-
1
def options
-
with_multiple_error_options_as_needed(@exception, pending_options || {})
-
end
-
-
1
def pending_options
-
if @execution_result.pending_fixed?
-
{
-
:description => "#{@example.full_description} FIXED",
-
:message_color => RSpec.configuration.fixed_color,
-
:failure_lines => [
-
"Expected pending '#{@execution_result.pending_message}' to fail. No Error was raised."
-
]
-
}
-
elsif @execution_result.status == :pending
-
{
-
:message_color => RSpec.configuration.pending_color,
-
:detail_formatter => PENDING_DETAIL_FORMATTER
-
}
-
end
-
end
-
-
1
def with_multiple_error_options_as_needed(exception, options)
-
return options unless multiple_exceptions_error?(exception)
-
-
options = options.merge(
-
:failure_lines => [],
-
:extra_detail_formatter => sub_failure_list_formatter(exception, options[:message_color]),
-
:detail_formatter => multiple_exception_summarizer(exception,
-
options[:detail_formatter],
-
options[:message_color])
-
)
-
-
return options unless exception.aggregation_metadata[:hide_backtrace]
-
options[:backtrace_formatter] = EmptyBacktraceFormatter
-
options
-
end
-
-
1
def multiple_exceptions_error?(exception)
-
MultipleExceptionError::InterfaceTag === exception
-
end
-
-
1
def multiple_exception_summarizer(exception, prior_detail_formatter, color)
-
lambda do |example, colorizer|
-
summary = if exception.aggregation_metadata[:hide_backtrace]
-
# Since the backtrace is hidden, the subfailures will come
-
# immediately after this, and using `:` will read well.
-
"Got #{exception.exception_count_description}:"
-
else
-
# The backtrace comes after this, so using a `:` doesn't make sense
-
# since the failures may be many lines below.
-
"#{exception.summary}."
-
end
-
-
summary = colorizer.wrap(summary, color || RSpec.configuration.failure_color)
-
return summary unless prior_detail_formatter
-
[
-
prior_detail_formatter.call(example, colorizer),
-
summary
-
]
-
end
-
end
-
-
1
def sub_failure_list_formatter(exception, message_color)
-
common_backtrace_truncater = CommonBacktraceTruncater.new(exception)
-
-
lambda do |failure_number, colorizer|
-
FlatMap.flat_map(exception.all_exceptions.each_with_index) do |failure, index|
-
options = with_multiple_error_options_as_needed(
-
failure,
-
:description => nil,
-
:indentation => 0,
-
:message_color => message_color || RSpec.configuration.failure_color,
-
:skip_shared_group_trace => true
-
)
-
-
failure = common_backtrace_truncater.with_truncated_backtrace(failure)
-
presenter = ExceptionPresenter.new(failure, @example, options)
-
presenter.fully_formatted_lines("#{failure_number}.#{index + 1}", colorizer)
-
end
-
end
-
end
-
-
# @private
-
# Used to prevent a confusing backtrace from showing up from the `aggregate_failures`
-
# block declared for `:aggregate_failures` metadata.
-
1
module EmptyBacktraceFormatter
-
1
def self.format_backtrace(*)
-
[]
-
end
-
end
-
-
# @private
-
1
class CommonBacktraceTruncater
-
1
def initialize(parent)
-
@parent = parent
-
end
-
-
1
def with_truncated_backtrace(child)
-
child_bt = child.backtrace
-
parent_bt = @parent.backtrace
-
return child if child_bt.nil? || child_bt.empty? || parent_bt.nil?
-
-
index_before_first_common_frame = -1.downto(-child_bt.size).find do |index|
-
parent_bt[index] != child_bt[index]
-
end
-
-
return child if index_before_first_common_frame == -1
-
-
child = child.dup
-
child.set_backtrace(child_bt[0..index_before_first_common_frame])
-
child
-
end
-
end
-
end
-
-
# @private
-
1
PENDING_DETAIL_FORMATTER = Proc.new do |example, colorizer|
-
colorizer.wrap("# #{example.execution_result.pending_message}", :detail)
-
end
-
end
-
end
-
-
# Provides a single exception instance that provides access to
-
# multiple sub-exceptions. This is used in situations where a single
-
# individual spec has multiple exceptions, such as one in the `it` block
-
# and one in an `after` block.
-
1
class MultipleExceptionError < StandardError
-
# @private
-
# Used so there is a common module in the ancestor chain of this class
-
# and `RSpec::Expectations::MultipleExpectationsNotMetError`, which allows
-
# code to detect exceptions that are instances of either, without first
-
# checking to see if rspec-expectations is loaded.
-
1
module InterfaceTag
-
# Appends the provided exception to the list.
-
# @param exception [Exception] Exception to append to the list.
-
# @private
-
1
def add(exception)
-
# `PendingExampleFixedError` can be assigned to an example that initially has no
-
# failures, but when the `aggregate_failures` around hook completes, it notifies of
-
# a failure. If we do not ignore `PendingExampleFixedError` it would be surfaced to
-
# the user as part of a multiple exception error, which is undesirable. While it's
-
# pretty weird we handle this here, it's the best solution I've been able to come
-
# up with, and `PendingExampleFixedError` always represents the _lack_ of any exception
-
# so clearly when we are transitioning to a `MultipleExceptionError`, it makes sense to
-
# ignore it.
-
return if Pending::PendingExampleFixedError === exception
-
-
all_exceptions << exception
-
-
if exception.class.name =~ /RSpec/
-
failures << exception
-
else
-
other_errors << exception
-
end
-
end
-
-
# Provides a way to force `ex` to be something that satisfies the multiple
-
# exception error interface. If it already satisfies it, it will be returned;
-
# otherwise it will wrap it in a `MultipleExceptionError`.
-
# @private
-
1
def self.for(ex)
-
return ex if self === ex
-
MultipleExceptionError.new(ex)
-
end
-
end
-
-
1
include InterfaceTag
-
-
# @return [Array<Exception>] The list of failures.
-
1
attr_reader :failures
-
-
# @return [Array<Exception>] The list of other errors.
-
1
attr_reader :other_errors
-
-
# @return [Array<Exception>] The list of failures and other exceptions, combined.
-
1
attr_reader :all_exceptions
-
-
# @return [Hash] Metadata used by RSpec for formatting purposes.
-
1
attr_reader :aggregation_metadata
-
-
# @return [nil] Provided only for interface compatibility with
-
# `RSpec::Expectations::MultipleExpectationsNotMetError`.
-
1
attr_reader :aggregation_block_label
-
-
# @param exceptions [Array<Exception>] The initial list of exceptions.
-
1
def initialize(*exceptions)
-
super()
-
-
@failures = []
-
@other_errors = []
-
@all_exceptions = []
-
@aggregation_metadata = { :hide_backtrace => true }
-
@aggregation_block_label = nil
-
-
exceptions.each { |e| add e }
-
end
-
-
# @return [String] Combines all the exception messages into a single string.
-
# @note RSpec does not actually use this -- instead it formats each exception
-
# individually.
-
1
def message
-
all_exceptions.map(&:message).join("\n\n")
-
end
-
-
# @return [String] A summary of the failure, including the block label and a count of failures.
-
1
def summary
-
"Got #{exception_count_description}"
-
end
-
-
# return [String] A description of the failure/error counts.
-
1
def exception_count_description
-
failure_count = Formatters::Helpers.pluralize(failures.size, "failure")
-
return failure_count if other_errors.empty?
-
error_count = Formatters::Helpers.pluralize(other_errors.size, "other error")
-
"#{failure_count} and #{error_count}"
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Core
-
1
module Formatters
-
# @api private
-
# Formatter for providing message output as a fallback when no other
-
# profiler implements #message
-
1
class FallbackMessageFormatter
-
1
Formatters.register self, :message
-
-
1
def initialize(output)
-
1
@output = output
-
end
-
-
# @private
-
1
attr_reader :output
-
-
# @api public
-
#
-
# Used by the reporter to send messages to the output stream.
-
#
-
# @param notification [MessageNotification] containing message
-
1
def message(notification)
-
output.puts notification.message
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_core "shell_escape"
-
-
1
module RSpec
-
1
module Core
-
1
module Formatters
-
# Formatters helpers.
-
1
module Helpers
-
# @private
-
1
SUB_SECOND_PRECISION = 5
-
-
# @private
-
1
DEFAULT_PRECISION = 2
-
-
# @api private
-
#
-
# Formats seconds into a human-readable string.
-
#
-
# @param duration [Float, Fixnum] in seconds
-
# @return [String] human-readable time
-
#
-
# @example
-
# format_duration(1) #=> "1 minute 1 second"
-
# format_duration(135.14) #=> "2 minutes 15.14 seconds"
-
1
def self.format_duration(duration)
-
precision = case
-
when duration < 1 then SUB_SECOND_PRECISION
-
when duration < 120 then DEFAULT_PRECISION
-
when duration < 300 then 1
-
else 0
-
end
-
-
if duration > 60
-
minutes = (duration.to_i / 60).to_i
-
seconds = duration - minutes * 60
-
-
"#{pluralize(minutes, 'minute')} #{pluralize(format_seconds(seconds, precision), 'second')}"
-
else
-
pluralize(format_seconds(duration, precision), 'second')
-
end
-
end
-
-
# @api private
-
#
-
# Formats seconds to have 5 digits of precision with trailing zeros
-
# removed if the number is less than 1 or with 2 digits of precision if
-
# the number is greater than zero.
-
#
-
# @param float [Float]
-
# @return [String] formatted float
-
#
-
# @example
-
# format_seconds(0.000006) #=> "0.00001"
-
# format_seconds(0.020000) #=> "0.02"
-
# format_seconds(1.00000000001) #=> "1"
-
#
-
# The precision used is set in {Helpers::SUB_SECOND_PRECISION} and
-
# {Helpers::DEFAULT_PRECISION}.
-
#
-
# @see #strip_trailing_zeroes
-
1
def self.format_seconds(float, precision=nil)
-
precision ||= (float < 1) ? SUB_SECOND_PRECISION : DEFAULT_PRECISION
-
formatted = "%.#{precision}f" % float
-
strip_trailing_zeroes(formatted)
-
end
-
-
# @api private
-
#
-
# Remove trailing zeros from a string.
-
#
-
# Only remove trailing zeros after a decimal place.
-
# see: http://rubular.com/r/ojtTydOgpn
-
#
-
# @param string [String] string with trailing zeros
-
# @return [String] string with trailing zeros removed
-
1
def self.strip_trailing_zeroes(string)
-
string.sub(/(?:(\..*[^0])0+|\.0+)$/, '\1')
-
end
-
1
private_class_method :strip_trailing_zeroes
-
-
# @api private
-
#
-
# Pluralize a word based on a count.
-
#
-
# @param count [Fixnum] number of objects
-
# @param string [String] word to be pluralized
-
# @return [String] pluralized word
-
1
def self.pluralize(count, string)
-
"#{count} #{string}#{'s' unless count.to_f == 1}"
-
end
-
-
# @api private
-
# Given a list of example ids, organizes them into a compact, ordered list.
-
1
def self.organize_ids(ids)
-
grouped = ids.inject(Hash.new { |h, k| h[k] = [] }) do |hash, id|
-
file, id = Example.parse_id(id)
-
hash[file] << id
-
hash
-
end
-
-
grouped.sort_by(&:first).map do |file, grouped_ids|
-
grouped_ids = grouped_ids.sort_by { |id| id.split(':').map(&:to_i) }
-
id = Metadata.id_from(:rerun_file_path => file, :scoped_id => grouped_ids.join(','))
-
ShellEscape.conditionally_quote(id)
-
end
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_core "source"
-
-
1
module RSpec
-
1
module Core
-
1
module Formatters
-
# @private
-
1
class SnippetExtractor
-
1
NoSuchFileError = Class.new(StandardError)
-
1
NoSuchLineError = Class.new(StandardError)
-
-
1
def self.extract_line_at(file_path, line_number)
-
source = source_from_file(file_path)
-
line = source.lines[line_number - 1]
-
raise NoSuchLineError unless line
-
line
-
end
-
-
1
def self.source_from_file(path)
-
raise NoSuchFileError unless File.exist?(path)
-
RSpec.world.source_cache.source_from_file(path)
-
end
-
-
1
if RSpec::Support::RubyFeatures.ripper_supported?
-
1
NoExpressionAtLineError = Class.new(StandardError)
-
-
1
attr_reader :source, :beginning_line_number, :max_line_count
-
-
1
def self.extract_expression_lines_at(file_path, beginning_line_number, max_line_count=nil)
-
if max_line_count == 1
-
[extract_line_at(file_path, beginning_line_number)]
-
else
-
source = source_from_file(file_path)
-
new(source, beginning_line_number, max_line_count).expression_lines
-
end
-
end
-
-
1
def initialize(source, beginning_line_number, max_line_count=nil)
-
@source = source
-
@beginning_line_number = beginning_line_number
-
@max_line_count = max_line_count
-
end
-
-
1
def expression_lines
-
line_range = line_range_of_expression
-
-
if max_line_count && line_range.count > max_line_count
-
line_range = (line_range.begin)..(line_range.begin + max_line_count - 1)
-
end
-
-
source.lines[(line_range.begin - 1)..(line_range.end - 1)]
-
rescue SyntaxError, NoExpressionAtLineError
-
[self.class.extract_line_at(source.path, beginning_line_number)]
-
end
-
-
1
private
-
-
1
def line_range_of_expression
-
@line_range_of_expression ||= begin
-
line_range = line_range_of_location_nodes_in_expression
-
initial_unclosed_tokens = unclosed_tokens_in_line_range(line_range)
-
unclosed_tokens = initial_unclosed_tokens
-
-
until (initial_unclosed_tokens & unclosed_tokens).empty?
-
line_range = (line_range.begin)..(line_range.end + 1)
-
unclosed_tokens = unclosed_tokens_in_line_range(line_range)
-
end
-
-
line_range
-
end
-
end
-
-
1
def unclosed_tokens_in_line_range(line_range)
-
tokens = FlatMap.flat_map(line_range) do |line_number|
-
source.tokens_by_line_number[line_number]
-
end
-
-
tokens.each_with_object([]) do |token, unclosed_tokens|
-
if token.opening?
-
unclosed_tokens << token
-
else
-
index = unclosed_tokens.rindex do |unclosed_token|
-
unclosed_token.closed_by?(token)
-
end
-
unclosed_tokens.delete_at(index) if index
-
end
-
end
-
end
-
-
1
def line_range_of_location_nodes_in_expression
-
line_numbers = expression_node.each_with_object(Set.new) do |node, set|
-
set << node.location.line if node.location
-
end
-
-
line_numbers.min..line_numbers.max
-
end
-
-
1
def expression_node
-
raise NoExpressionAtLineError if location_nodes_at_beginning_line.empty?
-
-
@expression_node ||= begin
-
common_ancestor_nodes = location_nodes_at_beginning_line.map do |node|
-
node.each_ancestor.to_a
-
end.reduce(:&)
-
-
common_ancestor_nodes.find { |node| expression_outmost_node?(node) }
-
end
-
end
-
-
1
def expression_outmost_node?(node)
-
return true unless node.parent
-
return false if node.type.to_s.start_with?('@')
-
![node, node.parent].all? do |n|
-
# See `Ripper::PARSER_EVENTS` for the complete list of sexp types.
-
type = n.type.to_s
-
type.end_with?('call') || type.start_with?('method_add_')
-
end
-
end
-
-
1
def location_nodes_at_beginning_line
-
source.nodes_by_line_number[beginning_line_number]
-
end
-
else
-
# :nocov:
-
skipped
def self.extract_expression_lines_at(file_path, beginning_line_number, *)
-
skipped
[extract_line_at(file_path, beginning_line_number)]
-
skipped
end
-
# :nocov:
-
end
-
-
1
def self.least_indentation_from(lines)
-
lines.map { |line| line[/^[ \t]*/] }.min
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Core
-
# Provides `before`, `after` and `around` hooks as a means of
-
# supporting common setup and teardown. This module is extended
-
# onto {ExampleGroup}, making the methods available from any `describe`
-
# or `context` block and included in {Configuration}, making them
-
# available off of the configuration object to define global setup
-
# or teardown logic.
-
1
module Hooks
-
# @api public
-
#
-
# @overload before(&block)
-
# @overload before(scope, &block)
-
# @param scope [Symbol] `:example`, `:context`, or `:suite`
-
# (defaults to `:example`)
-
# @overload before(scope, conditions, &block)
-
# @param scope [Symbol] `:example`, `:context`, or `:suite`
-
# (defaults to `:example`)
-
# @param conditions [Hash]
-
# constrains this hook to examples matching these conditions e.g.
-
# `before(:example, :ui => true) { ... }` will only run with examples
-
# or groups declared with `:ui => true`.
-
# @overload before(conditions, &block)
-
# @param conditions [Hash]
-
# constrains this hook to examples matching these conditions e.g.
-
# `before(:example, :ui => true) { ... }` will only run with examples
-
# or groups declared with `:ui => true`.
-
#
-
# @see #after
-
# @see #around
-
# @see ExampleGroup
-
# @see SharedContext
-
# @see SharedExampleGroup
-
# @see Configuration
-
#
-
# Declare a block of code to be run before each example (using `:example`)
-
# or once before any example (using `:context`). These are usually
-
# declared directly in the {ExampleGroup} to which they apply, but they
-
# can also be shared across multiple groups.
-
#
-
# You can also use `before(:suite)` to run a block of code before any
-
# example groups are run. This should be declared in {RSpec.configure}.
-
#
-
# Instance variables declared in `before(:example)` or `before(:context)`
-
# are accessible within each example.
-
#
-
# ### Order
-
#
-
# `before` hooks are stored in three scopes, which are run in order:
-
# `:suite`, `:context`, and `:example`. They can also be declared in
-
# several different places: `RSpec.configure`, a parent group, the current
-
# group. They are run in the following order:
-
#
-
# before(:suite) # Declared in RSpec.configure.
-
# before(:context) # Declared in RSpec.configure.
-
# before(:context) # Declared in a parent group.
-
# before(:context) # Declared in the current group.
-
# before(:example) # Declared in RSpec.configure.
-
# before(:example) # Declared in a parent group.
-
# before(:example) # Declared in the current group.
-
#
-
# If more than one `before` is declared within any one scope, they are run
-
# in the order in which they are declared.
-
#
-
# ### Conditions
-
#
-
# When you add a conditions hash to `before(:example)` or
-
# `before(:context)`, RSpec will only apply that hook to groups or
-
# examples that match the conditions. e.g.
-
#
-
# RSpec.configure do |config|
-
# config.before(:example, :authorized => true) do
-
# log_in_as :authorized_user
-
# end
-
# end
-
#
-
# describe Something, :authorized => true do
-
# # The before hook will run in before each example in this group.
-
# end
-
#
-
# describe SomethingElse do
-
# it "does something", :authorized => true do
-
# # The before hook will run before this example.
-
# end
-
#
-
# it "does something else" do
-
# # The hook will not run before this example.
-
# end
-
# end
-
#
-
# Note that filtered config `:context` hooks can still be applied
-
# to individual examples that have matching metadata. Just like
-
# Ruby's object model is that every object has a singleton class
-
# which has only a single instance, RSpec's model is that every
-
# example has a singleton example group containing just the one
-
# example.
-
#
-
# ### Warning: `before(:suite, :with => :conditions)`
-
#
-
# The conditions hash is used to match against specific examples. Since
-
# `before(:suite)` is not run in relation to any specific example or
-
# group, conditions passed along with `:suite` are effectively ignored.
-
#
-
# ### Exceptions
-
#
-
# When an exception is raised in a `before` block, RSpec skips any
-
# subsequent `before` blocks and the example, but runs all of the
-
# `after(:example)` and `after(:context)` hooks.
-
#
-
# ### Warning: implicit before blocks
-
#
-
# `before` hooks can also be declared in shared contexts which get
-
# included implicitly either by you or by extension libraries. Since
-
# RSpec runs these in the order in which they are declared within each
-
# scope, load order matters, and can lead to confusing results when one
-
# before block depends on state that is prepared in another before block
-
# that gets run later.
-
#
-
# ### Warning: `before(:context)`
-
#
-
# It is very tempting to use `before(:context)` to speed things up, but we
-
# recommend that you avoid this as there are a number of gotchas, as well
-
# as things that simply don't work.
-
#
-
# #### Context
-
#
-
# `before(:context)` is run in an example that is generated to provide
-
# group context for the block.
-
#
-
# #### Instance variables
-
#
-
# Instance variables declared in `before(:context)` are shared across all
-
# the examples in the group. This means that each example can change the
-
# state of a shared object, resulting in an ordering dependency that can
-
# make it difficult to reason about failures.
-
#
-
# #### Unsupported RSpec constructs
-
#
-
# RSpec has several constructs that reset state between each example
-
# automatically. These are not intended for use from within
-
# `before(:context)`:
-
#
-
# * `let` declarations
-
# * `subject` declarations
-
# * Any mocking, stubbing or test double declaration
-
#
-
# ### other frameworks
-
#
-
# Mock object frameworks and database transaction managers (like
-
# ActiveRecord) are typically designed around the idea of setting up
-
# before an example, running that one example, and then tearing down. This
-
# means that mocks and stubs can (sometimes) be declared in
-
# `before(:context)`, but get torn down before the first real example is
-
# ever run.
-
#
-
# You _can_ create database-backed model objects in a `before(:context)`
-
# in rspec-rails, but it will not be wrapped in a transaction for you, so
-
# you are on your own to clean up in an `after(:context)` block.
-
#
-
# @example before(:example) declared in an {ExampleGroup}
-
#
-
# describe Thing do
-
# before(:example) do
-
# @thing = Thing.new
-
# end
-
#
-
# it "does something" do
-
# # Here you can access @thing.
-
# end
-
# end
-
#
-
# @example before(:context) declared in an {ExampleGroup}
-
#
-
# describe Parser do
-
# before(:context) do
-
# File.open(file_to_parse, 'w') do |f|
-
# f.write <<-CONTENT
-
# stuff in the file
-
# CONTENT
-
# end
-
# end
-
#
-
# it "parses the file" do
-
# Parser.parse(file_to_parse)
-
# end
-
#
-
# after(:context) do
-
# File.delete(file_to_parse)
-
# end
-
# end
-
#
-
# @note The `:example` and `:context` scopes are also available as
-
# `:each` and `:all`, respectively. Use whichever you prefer.
-
# @note The `:scope` alias is only supported for hooks registered on
-
# `RSpec.configuration` since they exist independently of any
-
# example or example group.
-
1
def before(*args, &block)
-
2
hooks.register :append, :before, *args, &block
-
end
-
-
1
alias_method :append_before, :before
-
-
# Adds `block` to the front of the list of `before` blocks in the same
-
# scope (`:example`, `:context`, or `:suite`).
-
#
-
# See {#before} for scoping semantics.
-
1
def prepend_before(*args, &block)
-
hooks.register :prepend, :before, *args, &block
-
end
-
-
# @api public
-
# @overload after(&block)
-
# @overload after(scope, &block)
-
# @param scope [Symbol] `:example`, `:context`, or `:suite` (defaults to
-
# `:example`)
-
# @overload after(scope, conditions, &block)
-
# @param scope [Symbol] `:example`, `:context`, or `:suite` (defaults to
-
# `:example`)
-
# @param conditions [Hash]
-
# constrains this hook to examples matching these conditions e.g.
-
# `after(:example, :ui => true) { ... }` will only run with examples
-
# or groups declared with `:ui => true`.
-
# @overload after(conditions, &block)
-
# @param conditions [Hash]
-
# constrains this hook to examples matching these conditions e.g.
-
# `after(:example, :ui => true) { ... }` will only run with examples
-
# or groups declared with `:ui => true`.
-
#
-
# @see #before
-
# @see #around
-
# @see ExampleGroup
-
# @see SharedContext
-
# @see SharedExampleGroup
-
# @see Configuration
-
#
-
# Declare a block of code to be run after each example (using `:example`)
-
# or once after all examples n the context (using `:context`). See
-
# {#before} for more information about ordering.
-
#
-
# ### Exceptions
-
#
-
# `after` hooks are guaranteed to run even when there are exceptions in
-
# `before` hooks or examples. When an exception is raised in an after
-
# block, the exception is captured for later reporting, and subsequent
-
# `after` blocks are run.
-
#
-
# ### Order
-
#
-
# `after` hooks are stored in three scopes, which are run in order:
-
# `:example`, `:context`, and `:suite`. They can also be declared in
-
# several different places: `RSpec.configure`, a parent group, the current
-
# group. They are run in the following order:
-
#
-
# after(:example) # Declared in the current group.
-
# after(:example) # Declared in a parent group.
-
# after(:example) # Declared in RSpec.configure.
-
# after(:context) # Declared in the current group.
-
# after(:context) # Declared in a parent group.
-
# after(:context) # Declared in RSpec.configure.
-
# after(:suite) # Declared in RSpec.configure.
-
#
-
# This is the reverse of the order in which `before` hooks are run.
-
# Similarly, if more than one `after` is declared within any one scope,
-
# they are run in reverse order of that in which they are declared.
-
#
-
# @note The `:example` and `:context` scopes are also available as
-
# `:each` and `:all`, respectively. Use whichever you prefer.
-
# @note The `:scope` alias is only supported for hooks registered on
-
# `RSpec.configuration` since they exist independently of any
-
# example or example group.
-
1
def after(*args, &block)
-
hooks.register :prepend, :after, *args, &block
-
end
-
-
1
alias_method :prepend_after, :after
-
-
# Adds `block` to the back of the list of `after` blocks in the same
-
# scope (`:example`, `:context`, or `:suite`).
-
#
-
# See {#after} for scoping semantics.
-
1
def append_after(*args, &block)
-
hooks.register :append, :after, *args, &block
-
end
-
-
# @api public
-
# @overload around(&block)
-
# @overload around(scope, &block)
-
# @param scope [Symbol] `:example` (defaults to `:example`)
-
# present for syntax parity with `before` and `after`, but
-
# `:example`/`:each` is the only supported value.
-
# @overload around(scope, conditions, &block)
-
# @param scope [Symbol] `:example` (defaults to `:example`)
-
# present for syntax parity with `before` and `after`, but
-
# `:example`/`:each` is the only supported value.
-
# @param conditions [Hash] constrains this hook to examples matching
-
# these conditions e.g. `around(:example, :ui => true) { ... }` will
-
# only run with examples or groups declared with `:ui => true`.
-
# @overload around(conditions, &block)
-
# @param conditions [Hash] constrains this hook to examples matching
-
# these conditions e.g. `around(:example, :ui => true) { ... }` will
-
# only run with examples or groups declared with `:ui => true`.
-
#
-
# @yield [Example] the example to run
-
#
-
# @note the syntax of `around` is similar to that of `before` and `after`
-
# but the semantics are quite different. `before` and `after` hooks are
-
# run in the context of of the examples with which they are associated,
-
# whereas `around` hooks are actually responsible for running the
-
# examples. Consequently, `around` hooks do not have direct access to
-
# resources that are made available within the examples and their
-
# associated `before` and `after` hooks.
-
#
-
# @note `:example`/`:each` is the only supported scope.
-
#
-
# Declare a block of code, parts of which will be run before and parts
-
# after the example. It is your responsibility to run the example:
-
#
-
# around(:example) do |ex|
-
# # Do some stuff before.
-
# ex.run
-
# # Do some stuff after.
-
# end
-
#
-
# The yielded example aliases `run` with `call`, which lets you treat it
-
# like a `Proc`. This is especially handy when working with libaries
-
# that manage their own setup and teardown using a block or proc syntax,
-
# e.g.
-
#
-
# around(:example) {|ex| Database.transaction(&ex)}
-
# around(:example) {|ex| FakeFS(&ex)}
-
#
-
1
def around(*args, &block)
-
1
hooks.register :prepend, :around, *args, &block
-
end
-
-
# @private
-
# Holds the various registered hooks.
-
1
def hooks
-
219
@hooks ||= HookCollections.new(self, FilterableItemRepository::UpdateOptimized)
-
end
-
-
1
private
-
-
# @private
-
1
class Hook
-
1
attr_reader :block, :options
-
-
1
def initialize(block, options)
-
3
@block = block
-
3
@options = options
-
end
-
end
-
-
# @private
-
1
class BeforeHook < Hook
-
1
def run(example)
-
5
example.instance_exec(example, &block)
-
end
-
end
-
-
# @private
-
1
class AfterHook < Hook
-
1
def run(example)
-
example.instance_exec(example, &block)
-
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => ex
-
example.set_exception(ex)
-
end
-
end
-
-
# @private
-
1
class AfterContextHook < Hook
-
1
def run(example)
-
example.instance_exec(example, &block)
-
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e
-
# TODO: Come up with a better solution for this.
-
RSpec.configuration.reporter.message <<-EOS
-
-
An error occurred in an `after(:context)` hook.
-
#{e.class}: #{e.message}
-
occurred at #{e.backtrace.first}
-
-
EOS
-
end
-
end
-
-
# @private
-
1
class AroundHook < Hook
-
1
def execute_with(example, procsy)
-
example.instance_exec(procsy, &block)
-
return if procsy.executed?
-
Pending.mark_skipped!(example,
-
"#{hook_description} did not execute the example")
-
end
-
-
1
if Proc.method_defined?(:source_location)
-
1
def hook_description
-
"around hook at #{Metadata.relative_path(block.source_location.join(':'))}"
-
end
-
else # for 1.8.7
-
# :nocov:
-
skipped
def hook_description
-
skipped
"around hook"
-
skipped
end
-
# :nocov:
-
end
-
end
-
-
# @private
-
#
-
# This provides the primary API used by other parts of rspec-core. By hiding all
-
# implementation details behind this facade, it's allowed us to heavily optimize
-
# this, so that, for example, hook collection objects are only instantiated when
-
# a hook is added. This allows us to avoid many object allocations for the common
-
# case of a group having no hooks.
-
#
-
# This is only possible because this interface provides a "tell, don't ask"-style
-
# API, so that callers _tell_ this class what to do with the hooks, rather than
-
# asking this class for a list of hooks, and then doing something with them.
-
1
class HookCollections
-
1
def initialize(owner, filterable_item_repo_class)
-
21
@owner = owner
-
21
@filterable_item_repo_class = filterable_item_repo_class
-
21
@before_example_hooks = nil
-
21
@after_example_hooks = nil
-
21
@before_context_hooks = nil
-
21
@after_context_hooks = nil
-
21
@around_example_hooks = nil
-
end
-
-
1
def register_globals(host, globals)
-
11
parent_groups = host.parent_groups
-
-
11
process(host, parent_groups, globals, :before, :example, &:options)
-
11
process(host, parent_groups, globals, :after, :example, &:options)
-
11
process(host, parent_groups, globals, :around, :example, &:options)
-
-
11
process(host, parent_groups, globals, :before, :context, &:options)
-
11
process(host, parent_groups, globals, :after, :context, &:options)
-
end
-
-
1
def register_global_singleton_context_hooks(example, globals)
-
9
parent_groups = example.example_group.parent_groups
-
-
9
process(example, parent_groups, globals, :before, :context) { {} }
-
9
process(example, parent_groups, globals, :after, :context) { {} }
-
end
-
-
1
def register(prepend_or_append, position, *args, &block)
-
3
scope, options = scope_and_options_from(*args)
-
-
3
if scope == :suite
-
# TODO: consider making this an error in RSpec 4. For SemVer reasons,
-
# we are only warning in RSpec 3.
-
RSpec.warn_with "WARNING: `#{position}(:suite)` hooks are only supported on " \
-
"the RSpec configuration object. This " \
-
"`#{position}(:suite)` hook, registered on an example " \
-
"group, will be ignored."
-
return
-
end
-
-
3
hook = HOOK_TYPES[position][scope].new(block, options)
-
3
ensure_hooks_initialized_for(position, scope).__send__(prepend_or_append, hook, options)
-
end
-
-
# @private
-
#
-
# Runs all of the blocks stored with the hook in the context of the
-
# example. If no example is provided, just calls the hook directly.
-
1
def run(position, scope, example_or_group)
-
67
return if RSpec.configuration.dry_run?
-
-
67
if scope == :context
-
40
run_owned_hooks_for(position, :context, example_or_group)
-
else
-
27
case position
-
9
when :before then run_example_hooks_for(example_or_group, :before, :reverse_each)
-
9
when :after then run_example_hooks_for(example_or_group, :after, :each)
-
18
when :around then run_around_example_hooks_for(example_or_group) { yield }
-
end
-
end
-
end
-
-
1
SCOPES = [:example, :context]
-
-
1
SCOPE_ALIASES = { :each => :example, :all => :context }
-
-
1
HOOK_TYPES = {
-
2
:before => Hash.new { BeforeHook },
-
:after => Hash.new { AfterHook },
-
1
:around => Hash.new { AroundHook }
-
}
-
-
1
HOOK_TYPES[:after][:context] = AfterContextHook
-
-
1
protected
-
-
1
EMPTY_HOOK_ARRAY = [].freeze
-
-
1
def matching_hooks_for(position, scope, example_or_group)
-
350
repository = hooks_for(position, scope) { return EMPTY_HOOK_ARRAY }
-
-
# It would be nice to not have to switch on type here, but
-
# we don't want to define `ExampleGroup#metadata` because then
-
# `metadata` from within an individual example would return the
-
# group's metadata but the user would probably expect it to be
-
# the example's metadata.
-
14
metadata = case example_or_group
-
when ExampleGroup then example_or_group.class.metadata
-
14
else example_or_group.metadata
-
end
-
-
14
repository.items_for(metadata)
-
end
-
-
1
def all_hooks_for(position, scope)
-
101
hooks_for(position, scope) { return EMPTY_HOOK_ARRAY }.items_and_filters.map(&:first)
-
end
-
-
1
def run_owned_hooks_for(position, scope, example_or_group)
-
108
matching_hooks_for(position, scope, example_or_group).each do |hook|
-
5
hook.run(example_or_group)
-
end
-
end
-
-
1
def processable_hooks_for(position, scope, host)
-
73
if scope == :example
-
33
all_hooks_for(position, scope)
-
else
-
40
matching_hooks_for(position, scope, host)
-
end
-
end
-
-
1
private
-
-
1
def hooks_for(position, scope)
-
if position == :before
-
85
scope == :example ? @before_example_hooks : @before_context_hooks
-
elsif position == :after
-
85
scope == :example ? @after_example_hooks : @after_context_hooks
-
else # around
-
73
@around_example_hooks
-
243
end || yield
-
end
-
-
1
def ensure_hooks_initialized_for(position, scope)
-
4
if position == :before
-
2
if scope == :example
-
2
@before_example_hooks ||= @filterable_item_repo_class.new(:all?)
-
else
-
@before_context_hooks ||= @filterable_item_repo_class.new(:all?)
-
end
-
2
elsif position == :after
-
if scope == :example
-
@after_example_hooks ||= @filterable_item_repo_class.new(:all?)
-
else
-
@after_context_hooks ||= @filterable_item_repo_class.new(:all?)
-
end
-
else # around
-
2
@around_example_hooks ||= @filterable_item_repo_class.new(:all?)
-
end
-
end
-
-
1
def process(host, parent_groups, globals, position, scope)
-
73
hooks_to_process = globals.processable_hooks_for(position, scope, host)
-
73
return if hooks_to_process.empty?
-
-
11
hooks_to_process -= FlatMap.flat_map(parent_groups) do |group|
-
28
group.hooks.all_hooks_for(position, scope)
-
end
-
11
return if hooks_to_process.empty?
-
-
1
repository = ensure_hooks_initialized_for(position, scope)
-
2
hooks_to_process.each { |hook| repository.append hook, (yield hook) }
-
end
-
-
1
def scope_and_options_from(*args)
-
3
return :suite if args.first == :suite
-
3
scope = extract_scope_from(args)
-
3
meta = Metadata.build_hash_from(args, :warn_about_example_group_filtering)
-
3
return scope, meta
-
end
-
-
1
def extract_scope_from(args)
-
3
if known_scope?(args.first)
-
1
normalized_scope_for(args.shift)
-
2
elsif args.any? { |a| a.is_a?(Symbol) }
-
error_message = "You must explicitly give a scope " \
-
"(#{SCOPES.join(", ")}) or scope alias " \
-
"(#{SCOPE_ALIASES.keys.join(", ")}) when using symbols as " \
-
"metadata for a hook."
-
raise ArgumentError.new error_message
-
else
-
2
:example
-
end
-
end
-
-
1
def known_scope?(scope)
-
3
SCOPES.include?(scope) || SCOPE_ALIASES.keys.include?(scope)
-
end
-
-
1
def normalized_scope_for(scope)
-
1
SCOPE_ALIASES[scope] || scope
-
end
-
-
1
def run_example_hooks_for(example, position, each_method)
-
18
owner_parent_groups.__send__(each_method) do |group|
-
68
group.hooks.run_owned_hooks_for(position, :example, example)
-
end
-
end
-
-
1
def run_around_example_hooks_for(example)
-
9
hooks = FlatMap.flat_map(owner_parent_groups) do |group|
-
34
group.hooks.matching_hooks_for(:around, :example, example)
-
end
-
-
9
return yield if hooks.empty? # exit early to avoid the extra allocation cost of `Example::Procsy`
-
-
initial_procsy = Example::Procsy.new(example) { yield }
-
hooks.inject(initial_procsy) do |procsy, around_hook|
-
procsy.wrap { around_hook.execute_with(example, procsy) }
-
end.call
-
end
-
-
1
if respond_to?(:singleton_class) && singleton_class.ancestors.include?(singleton_class)
-
1
def owner_parent_groups
-
27
@owner.parent_groups
-
end
-
else # Ruby < 2.1 (see https://bugs.ruby-lang.org/issues/8035)
-
# :nocov:
-
skipped
def owner_parent_groups
-
skipped
@owner_parent_groups ||= [@owner] + @owner.parent_groups
-
skipped
end
-
# :nocov:
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support 'reentrant_mutex'
-
-
1
module RSpec
-
1
module Core
-
# This module is included in {ExampleGroup}, making the methods
-
# available to be called from within example blocks.
-
#
-
# @see ClassMethods
-
1
module MemoizedHelpers
-
# @note `subject` was contributed by Joe Ferris to support the one-liner
-
# syntax embraced by shoulda matchers:
-
#
-
# describe Widget do
-
# it { is_expected.to validate_presence_of(:name) }
-
# # or
-
# it { should validate_presence_of(:name) }
-
# end
-
#
-
# While the examples below demonstrate how to use `subject`
-
# explicitly in examples, we recommend that you define a method with
-
# an intention revealing name instead.
-
#
-
# @example
-
#
-
# # Explicit declaration of subject.
-
# describe Person do
-
# subject { Person.new(:birthdate => 19.years.ago) }
-
# it "should be eligible to vote" do
-
# subject.should be_eligible_to_vote
-
# # ^ ^ explicit reference to subject not recommended
-
# end
-
# end
-
#
-
# # Implicit subject => { Person.new }.
-
# describe Person do
-
# it "should be eligible to vote" do
-
# subject.should be_eligible_to_vote
-
# # ^ ^ explicit reference to subject not recommended
-
# end
-
# end
-
#
-
# # One-liner syntax - expectation is set on the subject.
-
# describe Person do
-
# it { is_expected.to be_eligible_to_vote }
-
# # or
-
# it { should be_eligible_to_vote }
-
# end
-
#
-
# @note Because `subject` is designed to create state that is reset
-
# between each example, and `before(:context)` is designed to setup
-
# state that is shared across _all_ examples in an example group,
-
# `subject` is _not_ intended to be used in a `before(:context)` hook.
-
#
-
# @see #should
-
# @see #should_not
-
# @see #is_expected
-
1
def subject
-
__memoized.fetch_or_store(:subject) do
-
described = described_class || self.class.metadata.fetch(:description_args).first
-
Class === described ? described.new : described
-
end
-
end
-
-
# When `should` is called with no explicit receiver, the call is
-
# delegated to the object returned by `subject`. Combined with an
-
# implicit subject this supports very concise expressions.
-
#
-
# @example
-
#
-
# describe Person do
-
# it { should be_eligible_to_vote }
-
# end
-
#
-
# @see #subject
-
# @see #is_expected
-
#
-
# @note This only works if you are using rspec-expectations.
-
# @note If you are using RSpec's newer expect-based syntax you may
-
# want to use `is_expected.to` instead of `should`.
-
1
def should(matcher=nil, message=nil)
-
RSpec::Expectations::PositiveExpectationHandler.handle_matcher(subject, matcher, message)
-
end
-
-
# Just like `should`, `should_not` delegates to the subject (implicit or
-
# explicit) of the example group.
-
#
-
# @example
-
#
-
# describe Person do
-
# it { should_not be_eligible_to_vote }
-
# end
-
#
-
# @see #subject
-
# @see #is_expected
-
#
-
# @note This only works if you are using rspec-expectations.
-
# @note If you are using RSpec's newer expect-based syntax you may
-
# want to use `is_expected.to_not` instead of `should_not`.
-
1
def should_not(matcher=nil, message=nil)
-
RSpec::Expectations::NegativeExpectationHandler.handle_matcher(subject, matcher, message)
-
end
-
-
# Wraps the `subject` in `expect` to make it the target of an expectation.
-
# Designed to read nicely for one-liners.
-
#
-
# @example
-
#
-
# describe [1, 2, 3] do
-
# it { is_expected.to be_an Array }
-
# it { is_expected.not_to include 4 }
-
# end
-
#
-
# @see #subject
-
# @see #should
-
# @see #should_not
-
#
-
# @note This only works if you are using rspec-expectations.
-
1
def is_expected
-
expect(subject)
-
end
-
-
# @private
-
# should just be placed in private section,
-
# but Ruby issues warnings on private attributes.
-
# and expanding it to the equivalent method upsets Rubocop,
-
# b/c it should obviously be a reader
-
1
attr_reader :__memoized
-
1
private :__memoized
-
-
1
private
-
-
# @private
-
1
def initialize(*)
-
32
__init_memoized
-
32
super
-
end
-
-
# @private
-
1
def __init_memoized
-
72
@__memoized = if RSpec.configuration.threadsafe?
-
72
ThreadsafeMemoized.new
-
else
-
NonThreadSafeMemoized.new
-
end
-
end
-
-
# @private
-
1
class ThreadsafeMemoized
-
1
def initialize
-
72
@memoized = {}
-
72
@mutex = Support::ReentrantMutex.new
-
end
-
-
1
def fetch_or_store(key)
-
@memoized.fetch(key) do # only first access pays for synchronization
-
@mutex.synchronize do
-
@memoized.fetch(key) { @memoized[key] = yield }
-
end
-
end
-
end
-
end
-
-
# @private
-
1
class NonThreadSafeMemoized
-
1
def initialize
-
@memoized = {}
-
end
-
-
1
def fetch_or_store(key)
-
@memoized.fetch(key) { @memoized[key] = yield }
-
end
-
end
-
-
# Used internally to customize the behavior of the
-
# memoized hash when used in a `before(:context)` hook.
-
#
-
# @private
-
1
class ContextHookMemoized
-
1
def self.isolate_for_context_hook(example_group_instance)
-
40
exploding_memoized = self
-
-
40
example_group_instance.instance_exec do
-
40
@__memoized = exploding_memoized
-
-
40
begin
-
40
yield
-
ensure
-
# This is doing a reset instead of just isolating for context hook.
-
# Really, this should set the old @__memoized back into place.
-
#
-
# Caller is the before and after context hooks
-
# which are both called from self.run
-
# I didn't look at why it made tests fail, maybe an object was getting reused in RSpec tests,
-
# if so, then that probably already works, and its the tests that are wrong.
-
40
__init_memoized
-
end
-
end
-
end
-
-
1
def self.fetch_or_store(key, &_block)
-
description = if key == :subject
-
"subject"
-
else
-
"let declaration `#{key}`"
-
end
-
-
raise <<-EOS
-
#{description} accessed in #{article} #{hook_expression} hook at:
-
#{CallerFilter.first_non_rspec_line}
-
-
`let` and `subject` declarations are not intended to be called
-
in #{article} #{hook_expression} hook, as they exist to define state that
-
is reset between each example, while #{hook_expression} exists to
-
#{hook_intention}.
-
EOS
-
end
-
-
# @private
-
1
class Before < self
-
1
def self.hook_expression
-
"`before(:context)`"
-
end
-
-
1
def self.article
-
"a"
-
end
-
-
1
def self.hook_intention
-
"define state that is shared across examples in an example group"
-
end
-
end
-
-
# @private
-
1
class After < self
-
1
def self.hook_expression
-
"`after(:context)`"
-
end
-
-
1
def self.article
-
"an"
-
end
-
-
1
def self.hook_intention
-
"cleanup state that is shared across examples in an example group"
-
end
-
end
-
end
-
-
# This module is extended onto {ExampleGroup}, making the methods
-
# available to be called from within example group blocks.
-
# You can think of them as being analagous to class macros.
-
1
module ClassMethods
-
# Generates a method whose return value is memoized after the first
-
# call. Useful for reducing duplication between examples that assign
-
# values to the same local variable.
-
#
-
# @note `let` _can_ enhance readability when used sparingly (1,2, or
-
# maybe 3 declarations) in any given example group, but that can
-
# quickly degrade with overuse. YMMV.
-
#
-
# @note `let` can be configured to be threadsafe or not.
-
# If it is threadsafe, it will take longer to access the value.
-
# If it is not threadsafe, it may behave in surprising ways in examples
-
# that spawn separate threads. Specify this on `RSpec.configure`
-
#
-
# @note Because `let` is designed to create state that is reset between
-
# each example, and `before(:context)` is designed to setup state that
-
# is shared across _all_ examples in an example group, `let` is _not_
-
# intended to be used in a `before(:context)` hook.
-
#
-
# @example
-
#
-
# describe Thing do
-
# let(:thing) { Thing.new }
-
#
-
# it "does something" do
-
# # First invocation, executes block, memoizes and returns result.
-
# thing.do_something
-
#
-
# # Second invocation, returns the memoized value.
-
# thing.should be_something
-
# end
-
# end
-
1
def let(name, &block)
-
# We have to pass the block directly to `define_method` to
-
# allow it to use method constructs like `super` and `return`.
-
raise "#let or #subject called without a block" if block.nil?
-
MemoizedHelpers.module_for(self).__send__(:define_method, name, &block)
-
-
# Apply the memoization. The method has been defined in an ancestor
-
# module so we can use `super` here to get the value.
-
if block.arity == 1
-
define_method(name) { __memoized.fetch_or_store(name) { super(RSpec.current_example, &nil) } }
-
else
-
define_method(name) { __memoized.fetch_or_store(name) { super(&nil) } }
-
end
-
end
-
-
# Just like `let`, except the block is invoked by an implicit `before`
-
# hook. This serves a dual purpose of setting up state and providing a
-
# memoized reference to that state.
-
#
-
# @example
-
#
-
# class Thing
-
# def self.count
-
# @count ||= 0
-
# end
-
#
-
# def self.count=(val)
-
# @count += val
-
# end
-
#
-
# def self.reset_count
-
# @count = 0
-
# end
-
#
-
# def initialize
-
# self.class.count += 1
-
# end
-
# end
-
#
-
# describe Thing do
-
# after(:example) { Thing.reset_count }
-
#
-
# context "using let" do
-
# let(:thing) { Thing.new }
-
#
-
# it "is not invoked implicitly" do
-
# Thing.count.should eq(0)
-
# end
-
#
-
# it "can be invoked explicitly" do
-
# thing
-
# Thing.count.should eq(1)
-
# end
-
# end
-
#
-
# context "using let!" do
-
# let!(:thing) { Thing.new }
-
#
-
# it "is invoked implicitly" do
-
# Thing.count.should eq(1)
-
# end
-
#
-
# it "returns memoized version on first invocation" do
-
# thing
-
# Thing.count.should eq(1)
-
# end
-
# end
-
# end
-
1
def let!(name, &block)
-
let(name, &block)
-
before { __send__(name) }
-
end
-
-
# Declares a `subject` for an example group which can then be wrapped
-
# with `expect` using `is_expected` to make it the target of an
-
# expectation in a concise, one-line example.
-
#
-
# Given a `name`, defines a method with that name which returns the
-
# `subject`. This lets you declare the subject once and access it
-
# implicitly in one-liners and explicitly using an intention revealing
-
# name.
-
#
-
# When given a `name`, calling `super` in the block is not supported.
-
#
-
# @note `subject` can be configured to be threadsafe or not.
-
# If it is threadsafe, it will take longer to access the value.
-
# If it is not threadsafe, it may behave in surprising ways in examples
-
# that spawn separate threads. Specify this on `RSpec.configure`
-
#
-
# @param name [String,Symbol] used to define an accessor with an
-
# intention revealing name
-
# @param block defines the value to be returned by `subject` in examples
-
#
-
# @example
-
#
-
# describe CheckingAccount, "with $50" do
-
# subject { CheckingAccount.new(Money.new(50, :USD)) }
-
# it { is_expected.to have_a_balance_of(Money.new(50, :USD)) }
-
# it { is_expected.not_to be_overdrawn }
-
# end
-
#
-
# describe CheckingAccount, "with a non-zero starting balance" do
-
# subject(:account) { CheckingAccount.new(Money.new(50, :USD)) }
-
# it { is_expected.not_to be_overdrawn }
-
# it "has a balance equal to the starting balance" do
-
# account.balance.should eq(Money.new(50, :USD))
-
# end
-
# end
-
#
-
# @see MemoizedHelpers#should
-
# @see MemoizedHelpers#should_not
-
# @see MemoizedHelpers#is_expected
-
1
def subject(name=nil, &block)
-
if name
-
let(name, &block)
-
alias_method :subject, name
-
-
self::NamedSubjectPreventSuper.__send__(:define_method, name) do
-
raise NotImplementedError, "`super` in named subjects is not supported"
-
end
-
else
-
let(:subject, &block)
-
end
-
end
-
-
# Just like `subject`, except the block is invoked by an implicit
-
# `before` hook. This serves a dual purpose of setting up state and
-
# providing a memoized reference to that state.
-
#
-
# @example
-
#
-
# class Thing
-
# def self.count
-
# @count ||= 0
-
# end
-
#
-
# def self.count=(val)
-
# @count += val
-
# end
-
#
-
# def self.reset_count
-
# @count = 0
-
# end
-
#
-
# def initialize
-
# self.class.count += 1
-
# end
-
# end
-
#
-
# describe Thing do
-
# after(:example) { Thing.reset_count }
-
#
-
# context "using subject" do
-
# subject { Thing.new }
-
#
-
# it "is not invoked implicitly" do
-
# Thing.count.should eq(0)
-
# end
-
#
-
# it "can be invoked explicitly" do
-
# subject
-
# Thing.count.should eq(1)
-
# end
-
# end
-
#
-
# context "using subject!" do
-
# subject!(:thing) { Thing.new }
-
#
-
# it "is invoked implicitly" do
-
# Thing.count.should eq(1)
-
# end
-
#
-
# it "returns memoized version on first invocation" do
-
# subject
-
# Thing.count.should eq(1)
-
# end
-
# end
-
# end
-
1
def subject!(name=nil, &block)
-
subject(name, &block)
-
before { subject }
-
end
-
end
-
-
# @private
-
#
-
# Gets the LetDefinitions module. The module is mixed into
-
# the example group and is used to hold all let definitions.
-
# This is done so that the block passed to `let` can be
-
# forwarded directly on to `define_method`, so that all method
-
# constructs (including `super` and `return`) can be used in
-
# a `let` block.
-
#
-
# The memoization is provided by a method definition on the
-
# example group that supers to the LetDefinitions definition
-
# in order to get the value to memoize.
-
1
def self.module_for(example_group)
-
11
get_constant_or_yield(example_group, :LetDefinitions) do
-
11
mod = Module.new do
-
11
include Module.new {
-
11
example_group.const_set(:NamedSubjectPreventSuper, self)
-
}
-
end
-
-
11
example_group.const_set(:LetDefinitions, mod)
-
11
mod
-
end
-
end
-
-
# @private
-
1
def self.define_helpers_on(example_group)
-
11
example_group.__send__(:include, module_for(example_group))
-
end
-
-
1
if Module.method(:const_defined?).arity == 1 # for 1.8
-
# @private
-
#
-
# Gets the named constant or yields.
-
# On 1.8, const_defined? / const_get do not take into
-
# account the inheritance hierarchy.
-
# :nocov:
-
skipped
def self.get_constant_or_yield(example_group, name)
-
skipped
if example_group.const_defined?(name)
-
skipped
example_group.const_get(name)
-
skipped
else
-
skipped
yield
-
skipped
end
-
skipped
end
-
# :nocov:
-
else
-
# @private
-
#
-
# Gets the named constant or yields.
-
# On 1.9, const_defined? / const_get take into account the
-
# the inheritance by default, and accept an argument to
-
# disable this behavior. It's important that we don't
-
# consider inheritance here; each example group level that
-
# uses a `let` should get its own `LetDefinitions` module.
-
1
def self.get_constant_or_yield(example_group, name)
-
11
if example_group.const_defined?(name, (check_ancestors = false))
-
example_group.const_get(name, check_ancestors)
-
else
-
11
yield
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Core
-
# Each ExampleGroup class and Example instance owns an instance of
-
# Metadata, which is Hash extended to support lazy evaluation of values
-
# associated with keys that may or may not be used by any example or group.
-
#
-
# In addition to metadata that is used internally, this also stores
-
# user-supplied metadata, e.g.
-
#
-
# describe Something, :type => :ui do
-
# it "does something", :slow => true do
-
# # ...
-
# end
-
# end
-
#
-
# `:type => :ui` is stored in the Metadata owned by the example group, and
-
# `:slow => true` is stored in the Metadata owned by the example. These can
-
# then be used to select which examples are run using the `--tag` option on
-
# the command line, or several methods on `Configuration` used to filter a
-
# run (e.g. `filter_run_including`, `filter_run_excluding`, etc).
-
#
-
# @see Example#metadata
-
# @see ExampleGroup.metadata
-
# @see FilterManager
-
# @see Configuration#filter_run_including
-
# @see Configuration#filter_run_excluding
-
1
module Metadata
-
# Matches strings either at the beginning of the input or prefixed with a
-
# whitespace, containing the current path, either postfixed with the
-
# separator, or at the end of the string. Match groups are the character
-
# before and the character after the string if any.
-
#
-
# http://rubular.com/r/fT0gmX6VJX
-
# http://rubular.com/r/duOrD4i3wb
-
# http://rubular.com/r/sbAMHFrOx1
-
1
def self.relative_path_regex
-
21
@relative_path_regex ||= /(\A|\s)#{File.expand_path('.')}(#{File::SEPARATOR}|\s|\Z)/
-
end
-
-
# @api private
-
#
-
# @param line [String] current code line
-
# @return [String] relative path to line
-
1
def self.relative_path(line)
-
21
line = line.sub(relative_path_regex, "\\1.\\2".freeze)
-
21
line = line.sub(/\A([^:]+:\d+)$/, '\\1'.freeze)
-
21
return nil if line == '-e:1'.freeze
-
21
line
-
rescue SecurityError
-
# :nocov:
-
skipped
nil
-
# :nocov:
-
end
-
-
# @private
-
# Iteratively walks up from the given metadata through all
-
# example group ancestors, yielding each metadata hash along the way.
-
1
def self.ascending(metadata)
-
yield metadata
-
return unless (group_metadata = metadata.fetch(:example_group) { metadata[:parent_example_group] })
-
-
loop do
-
yield group_metadata
-
break unless (group_metadata = group_metadata[:parent_example_group])
-
end
-
end
-
-
# @private
-
# Returns an enumerator that iteratively walks up the given metadata through all
-
# example group ancestors, yielding each metadata hash along the way.
-
1
def self.ascend(metadata)
-
enum_for(:ascending, metadata)
-
end
-
-
# @private
-
# Used internally to build a hash from an args array.
-
# Symbols are converted into hash keys with a value of `true`.
-
# This is done to support simple tagging using a symbol, rather
-
# than needing to do `:symbol => true`.
-
1
def self.build_hash_from(args, warn_about_example_group_filtering=false)
-
24
hash = args.last.is_a?(Hash) ? args.pop : {}
-
-
24
hash[args.pop] = true while args.last.is_a?(Symbol)
-
-
24
if warn_about_example_group_filtering && hash.key?(:example_group)
-
RSpec.deprecate("Filtering by an `:example_group` subhash",
-
:replacement => "the subhash to filter directly")
-
end
-
-
24
hash
-
end
-
-
# @private
-
1
def self.deep_hash_dup(object)
-
return object.dup if Array === object
-
return object unless Hash === object
-
-
object.inject(object.dup) do |duplicate, (key, value)|
-
duplicate[key] = deep_hash_dup(value)
-
duplicate
-
end
-
end
-
-
# @private
-
1
def self.id_from(metadata)
-
10
"#{metadata[:rerun_file_path]}[#{metadata[:scoped_id]}]"
-
end
-
-
# @private
-
1
def self.location_tuple_from(metadata)
-
[metadata[:absolute_file_path], metadata[:line_number]]
-
end
-
-
# @private
-
# Used internally to populate metadata hashes with computed keys
-
# managed by RSpec.
-
1
class HashPopulator
-
1
attr_reader :metadata, :user_metadata, :description_args, :block
-
-
1
def initialize(metadata, user_metadata, index_provider, description_args, block)
-
21
@metadata = metadata
-
21
@user_metadata = user_metadata
-
21
@index_provider = index_provider
-
21
@description_args = description_args
-
21
@block = block
-
end
-
-
1
def populate
-
21
ensure_valid_user_keys
-
-
21
metadata[:execution_result] = Example::ExecutionResult.new
-
21
metadata[:block] = block
-
21
metadata[:description_args] = description_args
-
21
metadata[:description] = build_description_from(*metadata[:description_args])
-
21
metadata[:full_description] = full_description
-
21
metadata[:described_class] = described_class
-
-
21
populate_location_attributes
-
21
metadata.update(user_metadata)
-
21
RSpec.configuration.apply_derived_metadata_to(metadata)
-
end
-
-
1
private
-
-
1
def populate_location_attributes
-
21
backtrace = user_metadata.delete(:caller)
-
-
21
file_path, line_number = if backtrace
-
file_path_and_line_number_from(backtrace)
-
elsif block.respond_to?(:source_location)
-
20
block.source_location
-
else
-
1
file_path_and_line_number_from(caller)
-
end
-
-
21
relative_file_path = Metadata.relative_path(file_path)
-
21
absolute_file_path = File.expand_path(relative_file_path)
-
21
metadata[:file_path] = relative_file_path
-
21
metadata[:line_number] = line_number.to_i
-
21
metadata[:location] = "#{relative_file_path}:#{line_number}"
-
21
metadata[:absolute_file_path] = absolute_file_path
-
21
metadata[:rerun_file_path] ||= relative_file_path
-
21
metadata[:scoped_id] = build_scoped_id_for(absolute_file_path)
-
end
-
-
1
def file_path_and_line_number_from(backtrace)
-
14
first_caller_from_outside_rspec = backtrace.find { |l| l !~ CallerFilter::LIB_REGEX }
-
1
first_caller_from_outside_rspec ||= backtrace.first
-
1
/(.+?):(\d+)(?:|:\d+)/.match(first_caller_from_outside_rspec).captures
-
end
-
-
1
def description_separator(parent_part, child_part)
-
20
if parent_part.is_a?(Module) && child_part =~ /^(#|::|\.)/
-
''.freeze
-
else
-
20
' '.freeze
-
end
-
end
-
-
1
def build_description_from(parent_description=nil, my_description=nil)
-
31
return parent_description.to_s unless my_description
-
10
separator = description_separator(parent_description, my_description)
-
10
(parent_description.to_s + separator) << my_description.to_s
-
end
-
-
1
def build_scoped_id_for(file_path)
-
21
index = @index_provider.call(file_path).to_s
-
23
parent_scoped_id = metadata.fetch(:scoped_id) { return index }
-
19
"#{parent_scoped_id}:#{index}"
-
end
-
-
1
def ensure_valid_user_keys
-
21
RESERVED_KEYS.each do |key|
-
336
next unless user_metadata.key?(key)
-
raise <<-EOM.gsub(/^\s+\|/, '')
-
|#{"*" * 50}
-
|:#{key} is not allowed
-
|
-
|RSpec reserves some hash keys for its own internal use,
-
|including :#{key}, which is used on:
-
|
-
| #{CallerFilter.first_non_rspec_line}.
-
|
-
|Here are all of RSpec's reserved hash keys:
-
|
-
| #{RESERVED_KEYS.join("\n ")}
-
|#{"*" * 50}
-
EOM
-
end
-
end
-
end
-
-
# @private
-
1
class ExampleHash < HashPopulator
-
1
def self.create(group_metadata, user_metadata, index_provider, description, block)
-
10
example_metadata = group_metadata.dup
-
10
group_metadata = Hash.new(&ExampleGroupHash.backwards_compatibility_default_proc do |hash|
-
hash[:parent_example_group]
-
end)
-
10
group_metadata.update(example_metadata)
-
-
10
example_metadata[:example_group] = group_metadata
-
10
example_metadata[:shared_group_inclusion_backtrace] = SharedExampleGroupInclusionStackFrame.current_backtrace
-
10
example_metadata.delete(:parent_example_group)
-
-
10
description_args = description.nil? ? [] : [description]
-
10
hash = new(example_metadata, user_metadata, index_provider, description_args, block)
-
10
hash.populate
-
10
hash.metadata
-
end
-
-
1
private
-
-
1
def described_class
-
10
metadata[:example_group][:described_class]
-
end
-
-
1
def full_description
-
10
build_description_from(
-
metadata[:example_group][:full_description],
-
metadata[:description]
-
)
-
end
-
end
-
-
# @private
-
1
class ExampleGroupHash < HashPopulator
-
1
def self.create(parent_group_metadata, user_metadata, example_group_index, *args, &block)
-
11
group_metadata = hash_with_backwards_compatibility_default_proc
-
-
11
if parent_group_metadata
-
10
group_metadata.update(parent_group_metadata)
-
10
group_metadata[:parent_example_group] = parent_group_metadata
-
end
-
-
11
hash = new(group_metadata, user_metadata, example_group_index, args, block)
-
11
hash.populate
-
11
hash.metadata
-
end
-
-
1
def self.hash_with_backwards_compatibility_default_proc
-
11
Hash.new(&backwards_compatibility_default_proc { |hash| hash })
-
end
-
-
1
def self.backwards_compatibility_default_proc(&example_group_selector)
-
21
Proc.new do |hash, key|
-
40
case key
-
when :example_group
-
# We commonly get here when rspec-core is applying a previously
-
# configured filter rule, such as when a gem configures:
-
#
-
# RSpec.configure do |c|
-
# c.include MyGemHelpers, :example_group => { :file_path => /spec\/my_gem_specs/ }
-
# end
-
#
-
# It's confusing for a user to get a deprecation at this point in
-
# the code, so instead we issue a deprecation from the config APIs
-
# that take a metadata hash, and MetadataFilter sets this thread
-
# local to silence the warning here since it would be so
-
# confusing.
-
unless RSpec::Support.thread_local_data[:silence_metadata_example_group_deprecations]
-
RSpec.deprecate("The `:example_group` key in an example group's metadata hash",
-
:replacement => "the example group's hash directly for the " \
-
"computed keys and `:parent_example_group` to access the parent " \
-
"example group metadata")
-
end
-
-
group_hash = example_group_selector.call(hash)
-
LegacyExampleGroupHash.new(group_hash) if group_hash
-
when :example_group_block
-
RSpec.deprecate("`metadata[:example_group_block]`",
-
:replacement => "`metadata[:block]`")
-
hash[:block]
-
when :describes
-
RSpec.deprecate("`metadata[:describes]`",
-
:replacement => "`metadata[:described_class]`")
-
hash[:described_class]
-
end
-
end
-
end
-
-
1
private
-
-
1
def described_class
-
11
candidate = metadata[:description_args].first
-
11
return candidate unless NilClass === candidate || String === candidate
-
10
parent_group = metadata[:parent_example_group]
-
10
parent_group && parent_group[:described_class]
-
end
-
-
1
def full_description
-
11
description = metadata[:description]
-
11
parent_example_group = metadata[:parent_example_group]
-
11
return description unless parent_example_group
-
-
10
parent_description = parent_example_group[:full_description]
-
10
separator = description_separator(parent_example_group[:description_args].last,
-
metadata[:description_args].first)
-
-
10
parent_description + separator + description
-
end
-
end
-
-
# @private
-
1
RESERVED_KEYS = [
-
:description,
-
:description_args,
-
:described_class,
-
:example_group,
-
:parent_example_group,
-
:execution_result,
-
:last_run_status,
-
:file_path,
-
:absolute_file_path,
-
:rerun_file_path,
-
:full_description,
-
:line_number,
-
:location,
-
:scoped_id,
-
:block,
-
:shared_group_inclusion_backtrace
-
]
-
end
-
-
# Mixin that makes the including class imitate a hash for backwards
-
# compatibility. The including class should use `attr_accessor` to
-
# declare attributes.
-
# @private
-
1
module HashImitatable
-
1
def self.included(klass)
-
2
klass.extend ClassMethods
-
end
-
-
1
def to_h
-
hash = extra_hash_attributes.dup
-
-
self.class.hash_attribute_names.each do |name|
-
hash[name] = __send__(name)
-
end
-
-
hash
-
end
-
-
1
(Hash.public_instance_methods - Object.public_instance_methods).each do |method_name|
-
93
next if [:[], :[]=, :to_h].include?(method_name.to_sym)
-
-
90
define_method(method_name) do |*args, &block|
-
issue_deprecation(method_name, *args)
-
-
hash = hash_for_delegation
-
self.class.hash_attribute_names.each do |name|
-
hash.delete(name) unless instance_variable_defined?(:"@#{name}")
-
end
-
-
hash.__send__(method_name, *args, &block).tap do
-
# apply mutations back to the object
-
hash.each do |name, value|
-
if directly_supports_attribute?(name)
-
set_value(name, value)
-
else
-
extra_hash_attributes[name] = value
-
end
-
end
-
end
-
end
-
end
-
-
1
def [](key)
-
issue_deprecation(:[], key)
-
-
if directly_supports_attribute?(key)
-
get_value(key)
-
else
-
extra_hash_attributes[key]
-
end
-
end
-
-
1
def []=(key, value)
-
issue_deprecation(:[]=, key, value)
-
-
if directly_supports_attribute?(key)
-
set_value(key, value)
-
else
-
extra_hash_attributes[key] = value
-
end
-
end
-
-
1
private
-
-
1
def extra_hash_attributes
-
@extra_hash_attributes ||= {}
-
end
-
-
1
def directly_supports_attribute?(name)
-
self.class.hash_attribute_names.include?(name)
-
end
-
-
1
def get_value(name)
-
__send__(name)
-
end
-
-
1
def set_value(name, value)
-
__send__(:"#{name}=", value)
-
end
-
-
1
def hash_for_delegation
-
to_h
-
end
-
-
1
def issue_deprecation(_method_name, *_args)
-
# no-op by default: subclasses can override
-
end
-
-
# @private
-
1
module ClassMethods
-
1
def hash_attribute_names
-
8
@hash_attribute_names ||= []
-
end
-
-
1
def attr_accessor(*names)
-
8
hash_attribute_names.concat(names)
-
8
super
-
end
-
end
-
end
-
-
# @private
-
# Together with the example group metadata hash default block,
-
# provides backwards compatibility for the old `:example_group`
-
# key. In RSpec 2.x, the computed keys of a group's metadata
-
# were exposed from a nested subhash keyed by `[:example_group]`, and
-
# then the parent group's metadata was exposed by sub-subhash
-
# keyed by `[:example_group][:example_group]`.
-
#
-
# In RSpec 3, we reorganized this to that the computed keys are
-
# exposed directly of the group metadata hash (no nesting), and
-
# `:parent_example_group` returns the parent group's metadata.
-
#
-
# Maintaining backwards compatibility was difficult: we wanted
-
# `:example_group` to return an object that:
-
#
-
# * Exposes the top-level metadata keys that used to be nested
-
# under `:example_group`.
-
# * Supports mutation (rspec-rails, for example, assigns
-
# `metadata[:example_group][:described_class]` when you use
-
# anonymous controller specs) such that changes are written
-
# back to the top-level metadata hash.
-
# * Exposes the parent group metadata as
-
# `[:example_group][:example_group]`.
-
1
class LegacyExampleGroupHash
-
1
include HashImitatable
-
-
1
def initialize(metadata)
-
@metadata = metadata
-
parent_group_metadata = metadata.fetch(:parent_example_group) { {} }[:example_group]
-
self[:example_group] = parent_group_metadata if parent_group_metadata
-
end
-
-
1
def to_h
-
super.merge(@metadata)
-
end
-
-
1
private
-
-
1
def directly_supports_attribute?(name)
-
name != :example_group
-
end
-
-
1
def get_value(name)
-
@metadata[name]
-
end
-
-
1
def set_value(name, value)
-
@metadata[name] = value
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Core
-
# Contains metadata filtering logic. This has been extracted from
-
# the metadata classes because it operates ON a metadata hash but
-
# does not manage any of the state in the hash. We're moving towards
-
# having metadata be a raw hash (not a custom subclass), so externalizing
-
# this filtering logic helps us move in that direction.
-
1
module MetadataFilter
-
1
class << self
-
# @private
-
1
def apply?(predicate, filters, metadata)
-
27
filters.__send__(predicate) { |k, v| filter_applies?(k, v, metadata) }
-
end
-
-
# @private
-
1
def filter_applies?(key, value, metadata)
-
9
silence_metadata_example_group_deprecations do
-
9
return location_filter_applies?(value, metadata) if key == :locations
-
9
return id_filter_applies?(value, metadata) if key == :ids
-
9
return filters_apply?(key, value, metadata) if Hash === value
-
-
9
return false unless metadata.key?(key)
-
return true if TrueClass === value && !!metadata[key]
-
return filter_applies_to_any_value?(key, value, metadata) if Array === metadata[key] && !(Proc === value)
-
-
case value
-
when Regexp
-
metadata[key] =~ value
-
when Proc
-
proc_filter_applies?(key, value, metadata)
-
else
-
metadata[key].to_s == value.to_s
-
end
-
end
-
end
-
-
1
private
-
-
1
def filter_applies_to_any_value?(key, value, metadata)
-
metadata[key].any? { |v| filter_applies?(key, v, key => value) }
-
end
-
-
1
def id_filter_applies?(rerun_paths_to_scoped_ids, metadata)
-
scoped_ids = rerun_paths_to_scoped_ids.fetch(metadata[:rerun_file_path]) { return false }
-
-
Metadata.ascend(metadata).any? do |meta|
-
scoped_ids.include?(meta[:scoped_id])
-
end
-
end
-
-
1
def location_filter_applies?(locations, metadata)
-
Metadata.ascend(metadata).any? do |meta|
-
file_path = meta[:absolute_file_path]
-
line_num = meta[:line_number]
-
-
locations[file_path].any? do |filter_line_num|
-
line_num == RSpec.world.preceding_declaration_line(file_path, filter_line_num)
-
end
-
end
-
end
-
-
1
def proc_filter_applies?(key, proc, metadata)
-
case proc.arity
-
when 0 then proc.call
-
when 2 then proc.call(metadata[key], metadata)
-
else proc.call(metadata[key])
-
end
-
end
-
-
1
def filters_apply?(key, value, metadata)
-
subhash = metadata[key]
-
return false unless Hash === subhash || HashImitatable === subhash
-
value.all? { |k, v| filter_applies?(k, v, subhash) }
-
end
-
-
1
def silence_metadata_example_group_deprecations
-
9
RSpec::Support.thread_local_data[:silence_metadata_example_group_deprecations] = true
-
9
yield
-
ensure
-
9
RSpec::Support.thread_local_data.delete(:silence_metadata_example_group_deprecations)
-
end
-
end
-
end
-
-
# Tracks a collection of filterable items (e.g. modules, hooks, etc)
-
# and provides an optimized API to get the applicable items for the
-
# metadata of an example or example group.
-
#
-
# There are two implementations, optimized for different uses.
-
# @private
-
1
module FilterableItemRepository
-
# This implementation is simple, and is optimized for frequent
-
# updates but rare queries. `append` and `prepend` do no extra
-
# processing, and no internal memoization is done, since this
-
# is not optimized for queries.
-
#
-
# This is ideal for use by a example or example group, which may
-
# be updated multiple times with globally configured hooks, etc,
-
# but will not be queried frequently by other examples or examle
-
# groups.
-
# @private
-
1
class UpdateOptimized
-
1
attr_reader :items_and_filters
-
-
1
def initialize(applies_predicate)
-
8
@applies_predicate = applies_predicate
-
8
@items_and_filters = []
-
end
-
-
1
def append(item, metadata)
-
4
@items_and_filters << [item, metadata]
-
end
-
-
1
def prepend(item, metadata)
-
1
@items_and_filters.unshift [item, metadata]
-
end
-
-
1
def items_for(request_meta)
-
18
@items_and_filters.each_with_object([]) do |(item, item_meta), to_return|
-
to_return << item if item_meta.empty? ||
-
15
MetadataFilter.apply?(@applies_predicate, item_meta, request_meta)
-
end
-
end
-
-
1
unless [].respond_to?(:each_with_object) # For 1.8.7
-
# :nocov:
-
skipped
undef items_for
-
skipped
def items_for(request_meta)
-
skipped
@items_and_filters.inject([]) do |to_return, (item, item_meta)|
-
skipped
to_return << item if item_meta.empty? ||
-
skipped
MetadataFilter.apply?(@applies_predicate, item_meta, request_meta)
-
skipped
to_return
-
skipped
end
-
skipped
end
-
# :nocov:
-
end
-
end
-
-
# This implementation is much more complex, and is optimized for
-
# rare (or hopefully no) updates once the queries start. Updates
-
# incur a cost as it has to clear the memoization and keep track
-
# of applicable keys. Queries will be O(N) the first time an item
-
# is provided with a given set of applicable metadata; subsequent
-
# queries with items with the same set of applicable metadata will
-
# be O(1) due to internal memoization.
-
#
-
# This is ideal for use by config, where filterable items (e.g. hooks)
-
# are typically added at the start of the process (e.g. in `spec_helper`)
-
# and then repeatedly queried as example groups and examples are defined.
-
# @private
-
1
class QueryOptimized < UpdateOptimized
-
1
alias find_items_for items_for
-
1
private :find_items_for
-
-
1
def initialize(applies_predicate)
-
5
super
-
5
@applicable_keys = Set.new
-
5
@proc_keys = Set.new
-
5
@memoized_lookups = Hash.new do |hash, applicable_metadata|
-
4
hash[applicable_metadata] = find_items_for(applicable_metadata)
-
end
-
end
-
-
1
def append(item, metadata)
-
1
super
-
1
handle_mutation(metadata)
-
end
-
-
1
def prepend(item, metadata)
-
1
super
-
1
handle_mutation(metadata)
-
end
-
-
1
def items_for(metadata)
-
# The filtering of `metadata` to `applicable_metadata` is the key thing
-
# that makes the memoization actually useful in practice, since each
-
# example and example group have different metadata (e.g. location and
-
# description). By filtering to the metadata keys our items care about,
-
# we can ignore extra metadata keys that differ for each example/group.
-
# For example, given `config.include DBHelpers, :db`, example groups
-
# can be split into these two sets: those that are tagged with `:db` and those
-
# that are not. For each set, this method for the first group in the set is
-
# still an `O(N)` calculation, but all subsequent groups in the set will be
-
# constant time lookups when they call this method.
-
63
applicable_metadata = applicable_metadata_from(metadata)
-
-
63
if applicable_metadata.any? { |k, _| @proc_keys.include?(k) }
-
# It's unsafe to memoize lookups involving procs (since they can
-
# be non-deterministic), so we skip the memoization in this case.
-
find_items_for(applicable_metadata)
-
else
-
63
@memoized_lookups[applicable_metadata]
-
end
-
end
-
-
1
private
-
-
1
def handle_mutation(metadata)
-
2
@applicable_keys.merge(metadata.keys)
-
2
@proc_keys.merge(proc_keys_from metadata)
-
2
@memoized_lookups.clear
-
end
-
-
1
def applicable_metadata_from(metadata)
-
63
@applicable_keys.inject({}) do |hash, key|
-
hash[key] = metadata[key] if metadata.key?(key)
-
hash
-
end
-
end
-
-
1
def proc_keys_from(metadata)
-
2
metadata.each_with_object([]) do |(key, value), to_return|
-
1
to_return << key if Proc === value
-
end
-
end
-
-
1
unless [].respond_to?(:each_with_object) # For 1.8.7
-
# :nocov:
-
skipped
undef proc_keys_from
-
skipped
def proc_keys_from(metadata)
-
skipped
metadata.inject([]) do |to_return, (key, value)|
-
skipped
to_return << key if Proc === value
-
skipped
to_return
-
skipped
end
-
skipped
end
-
# :nocov:
-
end
-
end
-
end
-
end
-
end
-
1
require 'rspec/mocks'
-
-
1
module RSpec
-
1
module Core
-
1
module MockingAdapters
-
# @private
-
1
module RSpec
-
1
include ::RSpec::Mocks::ExampleMethods
-
-
1
def self.framework_name
-
1
:rspec
-
end
-
-
1
def self.configuration
-
::RSpec::Mocks.configuration
-
end
-
-
1
def setup_mocks_for_rspec
-
9
::RSpec::Mocks.setup
-
end
-
-
1
def verify_mocks_for_rspec
-
9
::RSpec::Mocks.verify
-
end
-
-
1
def teardown_mocks_for_rspec
-
9
::RSpec::Mocks.teardown
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_core "formatters/exception_presenter"
-
1
RSpec::Support.require_rspec_core "formatters/helpers"
-
1
RSpec::Support.require_rspec_core "shell_escape"
-
-
1
module RSpec::Core
-
# Notifications are value objects passed to formatters to provide them
-
# with information about a particular event of interest.
-
1
module Notifications
-
# @private
-
1
module NullColorizer
-
1
module_function
-
-
1
def wrap(line, _code_or_symbol)
-
line
-
end
-
end
-
-
# The `StartNotification` represents a notification sent by the reporter
-
# when the suite is started. It contains the expected amount of examples
-
# to be executed, and the load time of RSpec.
-
#
-
# @attr count [Fixnum] the number counted
-
# @attr load_time [Float] the number of seconds taken to boot RSpec
-
# and load the spec files
-
1
StartNotification = Struct.new(:count, :load_time)
-
-
# The `ExampleNotification` represents notifications sent by the reporter
-
# which contain information about the current (or soon to be) example.
-
# It is used by formatters to access information about that example.
-
#
-
# @example
-
# def example_started(notification)
-
# puts "Hey I started #{notification.example.description}"
-
# end
-
#
-
# @attr example [RSpec::Core::Example] the current example
-
1
ExampleNotification = Struct.new(:example)
-
1
class ExampleNotification
-
# @private
-
1
def self.for(example)
-
27
execution_result = example.execution_result
-
-
27
return SkippedExampleNotification.new(example) if execution_result.example_skipped?
-
27
return new(example) unless execution_result.status == :pending || execution_result.status == :failed
-
-
klass = if execution_result.pending_fixed?
-
PendingExampleFixedNotification
-
elsif execution_result.status == :pending
-
PendingExampleFailedAsExpectedNotification
-
else
-
FailedExampleNotification
-
end
-
-
exception_presenter = Formatters::ExceptionPresenter::Factory.new(example).build
-
klass.new(example, exception_presenter)
-
end
-
-
1
private_class_method :new
-
end
-
-
# The `ExamplesNotification` represents notifications sent by the reporter
-
# which contain information about the suites examples.
-
#
-
# @example
-
# def stop(notification)
-
# puts "Hey I ran #{notification.examples.size}"
-
# end
-
#
-
1
class ExamplesNotification
-
1
def initialize(reporter)
-
3
@reporter = reporter
-
end
-
-
# @return [Array<RSpec::Core::Example>] list of examples
-
1
def examples
-
@reporter.examples
-
end
-
-
# @return [Array<RSpec::Core::Example>] list of failed examples
-
1
def failed_examples
-
@reporter.failed_examples
-
end
-
-
# @return [Array<RSpec::Core::Example>] list of pending examples
-
1
def pending_examples
-
@reporter.pending_examples
-
end
-
-
# @return [Array<RSpec::Core::Notifications::ExampleNotification>]
-
# returns examples as notifications
-
1
def notifications
-
@notifications ||= format_examples(examples)
-
end
-
-
# @return [Array<RSpec::Core::Notifications::FailedExampleNotification>]
-
# returns failed examples as notifications
-
1
def failure_notifications
-
@failed_notifications ||= format_examples(failed_examples)
-
end
-
-
# @return [Array<RSpec::Core::Notifications::SkippedExampleNotification,
-
# RSpec::Core::Notifications::PendingExampleFailedAsExpectedNotification>]
-
# returns pending examples as notifications
-
1
def pending_notifications
-
@pending_notifications ||= format_examples(pending_examples)
-
end
-
-
# @return [String] The list of failed examples, fully formatted in the way
-
# that RSpec's built-in formatters emit.
-
1
def fully_formatted_failed_examples(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
-
formatted = "\nFailures:\n"
-
-
failure_notifications.each_with_index do |failure, index|
-
formatted << failure.fully_formatted(index.next, colorizer)
-
end
-
-
formatted
-
end
-
-
# @return [String] The list of pending examples, fully formatted in the
-
# way that RSpec's built-in formatters emit.
-
1
def fully_formatted_pending_examples(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
-
formatted = "\nPending: (Failures listed here are expected and do not affect your suite's status)\n"
-
-
pending_notifications.each_with_index do |notification, index|
-
formatted << notification.fully_formatted(index.next, colorizer)
-
end
-
-
formatted
-
end
-
-
1
private
-
-
1
def format_examples(examples)
-
examples.map do |example|
-
ExampleNotification.for(example)
-
end
-
end
-
end
-
-
# The `FailedExampleNotification` extends `ExampleNotification` with
-
# things useful for examples that have failure info -- typically a
-
# failed or pending spec.
-
#
-
# @example
-
# def example_failed(notification)
-
# puts "Hey I failed :("
-
# puts "Here's my stack trace"
-
# puts notification.exception.backtrace.join("\n")
-
# end
-
#
-
# @attr [RSpec::Core::Example] example the current example
-
# @see ExampleNotification
-
1
class FailedExampleNotification < ExampleNotification
-
1
public_class_method :new
-
-
# @return [Exception] The example failure
-
1
def exception
-
@exception_presenter.exception
-
end
-
-
# @return [String] The example description
-
1
def description
-
@exception_presenter.description
-
end
-
-
# Returns the message generated for this failure line by line.
-
#
-
# @return [Array<String>] The example failure message
-
1
def message_lines
-
@exception_presenter.message_lines
-
end
-
-
# Returns the message generated for this failure colorized line by line.
-
#
-
# @param colorizer [#wrap] An object to colorize the message_lines by
-
# @return [Array<String>] The example failure message colorized
-
1
def colorized_message_lines(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
-
@exception_presenter.colorized_message_lines(colorizer)
-
end
-
-
# Returns the failures formatted backtrace.
-
#
-
# @return [Array<String>] the examples backtrace lines
-
1
def formatted_backtrace
-
@exception_presenter.formatted_backtrace
-
end
-
-
# Returns the failures colorized formatted backtrace.
-
#
-
# @param colorizer [#wrap] An object to colorize the message_lines by
-
# @return [Array<String>] the examples colorized backtrace lines
-
1
def colorized_formatted_backtrace(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
-
@exception_presenter.colorized_formatted_backtrace(colorizer)
-
end
-
-
# @return [String] The failure information fully formatted in the way that
-
# RSpec's built-in formatters emit.
-
1
def fully_formatted(failure_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes)
-
@exception_presenter.fully_formatted(failure_number, colorizer)
-
end
-
-
1
private
-
-
1
def initialize(example, exception_presenter=Formatters::ExceptionPresenter.new(example.execution_result.exception, example))
-
@exception_presenter = exception_presenter
-
super(example)
-
end
-
end
-
-
# @deprecated Use {FailedExampleNotification} instead.
-
1
class PendingExampleFixedNotification < FailedExampleNotification; end
-
-
# @deprecated Use {FailedExampleNotification} instead.
-
1
class PendingExampleFailedAsExpectedNotification < FailedExampleNotification; end
-
-
# The `SkippedExampleNotification` extends `ExampleNotification` with
-
# things useful for specs that are skipped.
-
#
-
# @attr [RSpec::Core::Example] example the current example
-
# @see ExampleNotification
-
1
class SkippedExampleNotification < ExampleNotification
-
1
public_class_method :new
-
-
# @return [String] The pending detail fully formatted in the way that
-
# RSpec's built-in formatters emit.
-
1
def fully_formatted(pending_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes)
-
formatted_caller = RSpec.configuration.backtrace_formatter.backtrace_line(example.location)
-
colorizer.wrap("\n #{pending_number}) #{example.full_description}", :pending) << "\n " <<
-
Formatters::ExceptionPresenter::PENDING_DETAIL_FORMATTER.call(example, colorizer) <<
-
"\n" << colorizer.wrap(" # #{formatted_caller}\n", :detail)
-
end
-
end
-
-
# The `GroupNotification` represents notifications sent by the reporter
-
# which contain information about the currently running (or soon to be)
-
# example group. It is used by formatters to access information about that
-
# group.
-
#
-
# @example
-
# def example_group_started(notification)
-
# puts "Hey I started #{notification.group.description}"
-
# end
-
# @attr group [RSpec::Core::ExampleGroup] the current group
-
1
GroupNotification = Struct.new(:group)
-
-
# The `MessageNotification` encapsulates generic messages that the reporter
-
# sends to formatters.
-
#
-
# @attr message [String] the message
-
1
MessageNotification = Struct.new(:message)
-
-
# The `SeedNotification` holds the seed used to randomize examples and
-
# whether that seed has been used or not.
-
#
-
# @attr seed [Fixnum] the seed used to randomize ordering
-
# @attr used [Boolean] whether the seed has been used or not
-
1
SeedNotification = Struct.new(:seed, :used)
-
1
class SeedNotification
-
# @api
-
# @return [Boolean] has the seed been used?
-
1
def seed_used?
-
2
!!used
-
end
-
1
private :used
-
-
# @return [String] The seed information fully formatted in the way that
-
# RSpec's built-in formatters emit.
-
1
def fully_formatted
-
"\nRandomized with seed #{seed}\n"
-
end
-
end
-
-
# The `SummaryNotification` holds information about the results of running
-
# a test suite. It is used by formatters to provide information at the end
-
# of the test run.
-
#
-
# @attr duration [Float] the time taken (in seconds) to run the suite
-
# @attr examples [Array<RSpec::Core::Example>] the examples run
-
# @attr failed_examples [Array<RSpec::Core::Example>] the failed examples
-
# @attr pending_examples [Array<RSpec::Core::Example>] the pending examples
-
# @attr load_time [Float] the number of seconds taken to boot RSpec
-
# and load the spec files
-
1
SummaryNotification = Struct.new(:duration, :examples, :failed_examples,
-
:pending_examples, :load_time)
-
1
class SummaryNotification
-
# @api
-
# @return [Fixnum] the number of examples run
-
1
def example_count
-
1
@example_count ||= examples.size
-
end
-
-
# @api
-
# @return [Fixnum] the number of failed examples
-
1
def failure_count
-
1
@failure_count ||= failed_examples.size
-
end
-
-
# @api
-
# @return [Fixnum] the number of pending examples
-
1
def pending_count
-
1
@pending_count ||= pending_examples.size
-
end
-
-
# @api
-
# @return [String] A line summarising the result totals of the spec run.
-
1
def totals_line
-
summary = Formatters::Helpers.pluralize(example_count, "example")
-
summary << ", " << Formatters::Helpers.pluralize(failure_count, "failure")
-
summary << ", #{pending_count} pending" if pending_count > 0
-
summary
-
end
-
-
# @api public
-
#
-
# Wraps the results line with colors based on the configured
-
# colors for failure, pending, and success. Defaults to red,
-
# yellow, green accordingly.
-
#
-
# @param colorizer [#wrap] An object which supports wrapping text with
-
# specific colors.
-
# @return [String] A colorized results line.
-
1
def colorized_totals_line(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
-
if failure_count > 0
-
colorizer.wrap(totals_line, RSpec.configuration.failure_color)
-
elsif pending_count > 0
-
colorizer.wrap(totals_line, RSpec.configuration.pending_color)
-
else
-
colorizer.wrap(totals_line, RSpec.configuration.success_color)
-
end
-
end
-
-
# @api public
-
#
-
# Formats failures into a rerunable command format.
-
#
-
# @param colorizer [#wrap] An object which supports wrapping text with
-
# specific colors.
-
# @return [String] A colorized summary line.
-
1
def colorized_rerun_commands(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
-
"\nFailed examples:\n\n" +
-
failed_examples.map do |example|
-
colorizer.wrap("rspec #{rerun_argument_for(example)}", RSpec.configuration.failure_color) + " " +
-
colorizer.wrap("# #{example.full_description}", RSpec.configuration.detail_color)
-
end.join("\n")
-
end
-
-
# @return [String] a formatted version of the time it took to run the
-
# suite
-
1
def formatted_duration
-
Formatters::Helpers.format_duration(duration)
-
end
-
-
# @return [String] a formatted version of the time it took to boot RSpec
-
# and load the spec files
-
1
def formatted_load_time
-
Formatters::Helpers.format_duration(load_time)
-
end
-
-
# @return [String] The summary information fully formatted in the way that
-
# RSpec's built-in formatters emit.
-
1
def fully_formatted(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
-
formatted = "\nFinished in #{formatted_duration} " \
-
"(files took #{formatted_load_time} to load)\n" \
-
"#{colorized_totals_line(colorizer)}\n"
-
-
unless failed_examples.empty?
-
formatted << colorized_rerun_commands(colorizer) << "\n"
-
end
-
-
formatted
-
end
-
-
1
private
-
-
1
include RSpec::Core::ShellEscape
-
-
1
def rerun_argument_for(example)
-
location = example.location_rerun_argument
-
return location unless duplicate_rerun_locations.include?(location)
-
conditionally_quote(example.id)
-
end
-
-
1
def duplicate_rerun_locations
-
@duplicate_rerun_locations ||= begin
-
locations = RSpec.world.all_examples.map(&:location_rerun_argument)
-
-
Set.new.tap do |s|
-
locations.group_by { |l| l }.each do |l, ls|
-
s << l if ls.count > 1
-
end
-
end
-
end
-
end
-
end
-
-
# The `ProfileNotification` holds information about the results of running a
-
# test suite when profiling is enabled. It is used by formatters to provide
-
# information at the end of the test run for profiling information.
-
#
-
# @attr duration [Float] the time taken (in seconds) to run the suite
-
# @attr examples [Array<RSpec::Core::Example>] the examples run
-
# @attr number_of_examples [Fixnum] the number of examples to profile
-
# @attr example_groups [Array<RSpec::Core::Profiler>] example groups run
-
1
class ProfileNotification
-
1
def initialize(duration, examples, number_of_examples, example_groups)
-
@duration = duration
-
@examples = examples
-
@number_of_examples = number_of_examples
-
@example_groups = example_groups
-
end
-
1
attr_reader :duration, :examples, :number_of_examples
-
-
# @return [Array<RSpec::Core::Example>] the slowest examples
-
1
def slowest_examples
-
@slowest_examples ||=
-
examples.sort_by do |example|
-
-example.execution_result.run_time
-
end.first(number_of_examples)
-
end
-
-
# @return [Float] the time taken (in seconds) to run the slowest examples
-
1
def slow_duration
-
@slow_duration ||=
-
slowest_examples.inject(0.0) do |i, e|
-
i + e.execution_result.run_time
-
end
-
end
-
-
# @return [String] the percentage of total time taken
-
1
def percentage
-
@percentage ||=
-
begin
-
time_taken = slow_duration / duration
-
'%.1f' % ((time_taken.nan? ? 0.0 : time_taken) * 100)
-
end
-
end
-
-
# @return [Array<RSpec::Core::Example>] the slowest example groups
-
1
def slowest_groups
-
@slowest_groups ||= calculate_slowest_groups
-
end
-
-
1
private
-
-
1
def calculate_slowest_groups
-
# stop if we've only one example group
-
return {} if @example_groups.keys.length <= 1
-
-
@example_groups.each_value do |hash|
-
hash[:average] = hash[:total_time].to_f / hash[:count]
-
end
-
-
groups = @example_groups.sort_by { |_, hash| -hash[:average] }.first(number_of_examples)
-
groups.map { |group, data| [group.location, data] }
-
end
-
end
-
-
# The `DeprecationNotification` is issued by the reporter when a deprecated
-
# part of RSpec is encountered. It represents information about the
-
# deprecated call site.
-
#
-
# @attr message [String] A custom message about the deprecation
-
# @attr deprecated [String] A custom message about the deprecation (alias of
-
# message)
-
# @attr replacement [String] An optional replacement for the deprecation
-
# @attr call_site [String] An optional call site from which the deprecation
-
# was issued
-
1
DeprecationNotification = Struct.new(:deprecated, :message, :replacement, :call_site)
-
1
class DeprecationNotification
-
1
private_class_method :new
-
-
# @api
-
# Convenience way to initialize the notification
-
1
def self.from_hash(data)
-
new data[:deprecated], data[:message], data[:replacement], data[:call_site]
-
end
-
end
-
-
# `NullNotification` represents a placeholder value for notifications that
-
# currently require no information, but we may wish to extend in future.
-
1
class NullNotification
-
end
-
-
# `CustomNotification` is used when sending custom events to formatters /
-
# other registered listeners, it creates attributes based on supplied hash
-
# of options.
-
1
class CustomNotification < Struct
-
# @param options [Hash] A hash of method / value pairs to create on this notification
-
# @return [CustomNotification]
-
#
-
# Build a custom notification based on the supplied option key / values.
-
1
def self.for(options={})
-
return NullNotification if options.keys.empty?
-
new(*options.keys).new(*options.values)
-
end
-
end
-
end
-
end
-
# http://www.ruby-doc.org/stdlib/libdoc/optparse/rdoc/classes/OptionParser.html
-
1
require 'optparse'
-
-
1
module RSpec::Core
-
# @private
-
1
class Parser
-
1
def self.parse(args, source=nil)
-
4
new(args).parse(source)
-
end
-
-
1
attr_reader :original_args
-
-
1
def initialize(original_args)
-
4
@original_args = original_args
-
end
-
-
1
def parse(source=nil)
-
4
return { :files_or_directories_to_run => [] } if original_args.empty?
-
2
args = original_args.dup
-
-
2
options = args.delete('--tty') ? { :tty => true } : {}
-
2
begin
-
2
parser(options).parse!(args)
-
rescue OptionParser::InvalidOption => e
-
failure = e.message
-
failure << " (defined in #{source})" if source
-
abort "#{failure}\n\nPlease use --help for a listing of valid options"
-
end
-
-
2
options[:files_or_directories_to_run] = args
-
2
options
-
end
-
-
1
private
-
-
# rubocop:disable MethodLength
-
# rubocop:disable Metrics/AbcSize
-
# rubocop:disable CyclomaticComplexity
-
1
def parser(options)
-
2
OptionParser.new do |parser|
-
2
parser.banner = "Usage: rspec [options] [files or directories]\n\n"
-
-
2
parser.on('-I PATH', 'Specify PATH to add to $LOAD_PATH (may be used more than once).') do |dirs|
-
options[:libs] ||= []
-
options[:libs].concat(dirs.split(File::PATH_SEPARATOR))
-
end
-
-
2
parser.on('-r', '--require PATH', 'Require a file.') do |path|
-
1
options[:requires] ||= []
-
1
options[:requires] << path
-
end
-
-
2
parser.on('-O', '--options PATH', 'Specify the path to a custom options file.') do |path|
-
options[:custom_options_file] = path
-
end
-
-
parser.on('--order TYPE[:SEED]', 'Run examples by the specified order type.',
-
' [defined] examples and groups are run in the order they are defined',
-
' [rand] randomize the order of groups and examples',
-
' [random] alias for rand',
-
2
' [random:SEED] e.g. --order random:123') do |o|
-
options[:order] = o
-
end
-
-
2
parser.on('--seed SEED', Integer, 'Equivalent of --order rand:SEED.') do |seed|
-
options[:order] = "rand:#{seed}"
-
end
-
-
parser.on('--bisect[=verbose]', 'Repeatedly runs the suite in order to isolate the failures to the ',
-
2
' smallest reproducible case.') do |argument|
-
bisect_and_exit(argument)
-
end
-
-
2
parser.on('--[no-]fail-fast[=COUNT]', 'Abort the run after a certain number of failures (1 by default).') do |argument|
-
if argument == true
-
value = 1
-
elsif argument == false || argument == 0
-
value = false
-
else
-
begin
-
value = Integer(argument)
-
rescue ArgumentError
-
RSpec.warning "Expected an integer value for `--fail-fast`, got: #{argument.inspect}", :call_site => nil
-
end
-
end
-
set_fail_fast(options, value)
-
end
-
-
parser.on('--failure-exit-code CODE', Integer,
-
2
'Override the exit code used when there are failing specs.') do |code|
-
options[:failure_exit_code] = code
-
end
-
-
parser.on('--dry-run', 'Print the formatter output of your suite without',
-
2
' running any examples or hooks') do |_o|
-
options[:dry_run] = true
-
end
-
-
2
parser.on('-X', '--[no-]drb', 'Run examples via DRb.') do |o|
-
options[:drb] = o
-
end
-
-
2
parser.on('--drb-port PORT', 'Port to connect to the DRb server.') do |o|
-
options[:drb_port] = o.to_i
-
end
-
-
2
parser.on('--init', 'Initialize your project with RSpec.') do |_cmd|
-
initialize_project_and_exit
-
end
-
-
2
parser.separator("\n **** Output ****\n\n")
-
-
parser.on('-f', '--format FORMATTER', 'Choose a formatter.',
-
' [p]rogress (default - dots)',
-
' [d]ocumentation (group and example names)',
-
' [h]tml',
-
' [j]son',
-
2
' custom formatter class name') do |o|
-
2
options[:formatters] ||= []
-
2
options[:formatters] << [o]
-
end
-
-
parser.on('-o', '--out FILE',
-
'Write output to a file instead of $stdout. This option applies',
-
' to the previously specified --format, or the default format',
-
' if no format is specified.'
-
2
) do |o|
-
options[:formatters] ||= [['progress']]
-
options[:formatters].last << o
-
end
-
-
2
parser.on('--deprecation-out FILE', 'Write deprecation warnings to a file instead of $stderr.') do |file|
-
options[:deprecation_stream] = file
-
end
-
-
2
parser.on('-b', '--backtrace', 'Enable full backtrace.') do |_o|
-
options[:full_backtrace] = true
-
end
-
-
2
parser.on('-c', '--[no-]color', '--[no-]colour', 'Enable color in the output.') do |o|
-
1
options[:color] = o
-
end
-
-
parser.on('-p', '--[no-]profile [COUNT]',
-
2
'Enable profiling of examples and list the slowest examples (default: 10).') do |argument|
-
options[:profile_examples] = if argument.nil?
-
true
-
elsif argument == false
-
false
-
else
-
begin
-
Integer(argument)
-
rescue ArgumentError
-
RSpec.warning "Non integer specified as profile count, seperate " \
-
"your path from options with -- e.g. " \
-
"`rspec --profile -- #{argument}`",
-
:call_site => nil
-
true
-
end
-
end
-
end
-
-
2
parser.on('-w', '--warnings', 'Enable ruby warnings') do
-
$VERBOSE = true
-
end
-
-
2
parser.separator <<-FILTERING
-
-
**** Filtering/tags ****
-
-
In addition to the following options for selecting specific files, groups, or
-
examples, you can select individual examples by appending the line number(s) to
-
the filename:
-
-
rspec path/to/a_spec.rb:37:87
-
-
You can also pass example ids enclosed in square brackets:
-
-
rspec path/to/a_spec.rb[1:5,1:6] # run the 5th and 6th examples/groups defined in the 1st group
-
-
FILTERING
-
-
2
parser.on('--only-failures', "Filter to just the examples that failed the last time they ran.") do
-
configure_only_failures(options)
-
end
-
-
parser.on("--next-failure", "Apply `--only-failures` and abort after one failure.",
-
2
" (Equivalent to `--only-failures --fail-fast --order defined`)") do
-
configure_only_failures(options)
-
set_fail_fast(options, 1)
-
options[:order] ||= 'defined'
-
end
-
-
2
parser.on('-P', '--pattern PATTERN', 'Load files matching pattern (default: "spec/**/*_spec.rb").') do |o|
-
if options[:pattern]
-
options[:pattern] += ',' + o
-
else
-
options[:pattern] = o
-
end
-
end
-
-
parser.on('--exclude-pattern PATTERN',
-
2
'Load files except those matching pattern. Opposite effect of --pattern.') do |o|
-
options[:exclude_pattern] = o
-
end
-
-
parser.on('-e', '--example STRING', "Run examples whose full nested names include STRING (may be",
-
2
" used more than once)") do |o|
-
(options[:full_description] ||= []) << Regexp.compile(Regexp.escape(o))
-
end
-
-
parser.on('-t', '--tag TAG[:VALUE]',
-
'Run examples with the specified tag, or exclude examples',
-
'by adding ~ before the tag.',
-
' - e.g. ~slow',
-
2
' - TAG is always converted to a symbol') do |tag|
-
filter_type = tag =~ /^~/ ? :exclusion_filter : :inclusion_filter
-
-
name, value = tag.gsub(/^(~@|~|@)/, '').split(':', 2)
-
name = name.to_sym
-
-
parsed_value = case value
-
when nil then true # The default value for tags is true
-
when 'true' then true
-
when 'false' then false
-
when 'nil' then nil
-
when /^:/ then value[1..-1].to_sym
-
when /^\d+$/ then Integer(value)
-
when /^\d+.\d+$/ then Float(value)
-
else
-
value
-
end
-
-
add_tag_filter(options, filter_type, name, parsed_value)
-
end
-
-
parser.on('--default-path PATH', 'Set the default path where RSpec looks for examples (can',
-
2
' be a path to a file or a directory).') do |path|
-
options[:default_path] = path
-
end
-
-
2
parser.separator("\n **** Utility ****\n\n")
-
-
2
parser.on('-v', '--version', 'Display the version.') do
-
print_version_and_exit
-
end
-
-
# These options would otherwise be confusing to users, so we forcibly
-
# prevent them from executing.
-
#
-
# * --I is too similar to -I.
-
# * -d was a shorthand for --debugger, which is removed, but now would
-
# trigger --default-path.
-
2
invalid_options = %w[-d --I]
-
-
2
parser.on_tail('-h', '--help', "You're looking at it.") do
-
print_help_and_exit(parser, invalid_options)
-
end
-
-
# This prevents usage of the invalid_options.
-
2
invalid_options.each do |option|
-
4
parser.on(option) do
-
raise OptionParser::InvalidOption.new
-
end
-
end
-
end
-
end
-
# rubocop:enable Metrics/AbcSize
-
# rubocop:enable MethodLength
-
# rubocop:enable CyclomaticComplexity
-
-
1
def add_tag_filter(options, filter_type, tag_name, value=true)
-
(options[filter_type] ||= {})[tag_name] = value
-
end
-
-
1
def set_fail_fast(options, value)
-
options[:fail_fast] = value
-
end
-
-
1
def configure_only_failures(options)
-
options[:only_failures] = true
-
add_tag_filter(options, :inclusion_filter, :last_run_status, 'failed')
-
end
-
-
1
def initialize_project_and_exit
-
RSpec::Support.require_rspec_core "project_initializer"
-
ProjectInitializer.new.run
-
exit
-
end
-
-
1
def bisect_and_exit(argument)
-
RSpec::Support.require_rspec_core "bisect/coordinator"
-
-
success = Bisect::Coordinator.bisect_with(
-
original_args,
-
RSpec.configuration,
-
bisect_formatter_for(argument)
-
)
-
-
exit(success ? 0 : 1)
-
end
-
-
1
def bisect_formatter_for(argument)
-
return Formatters::BisectDebugFormatter if argument == "verbose"
-
Formatters::BisectProgressFormatter
-
end
-
-
1
def print_version_and_exit
-
puts RSpec::Core::Version::STRING
-
exit
-
end
-
-
1
def print_help_and_exit(parser, invalid_options)
-
# Removing the blank invalid options from the output.
-
puts parser.to_s.gsub(/^\s+(#{invalid_options.join('|')})\s*$\n/, '')
-
exit
-
end
-
end
-
end
-
1
module RSpec
-
1
module Core
-
# @private
-
1
module Ordering
-
# @private
-
# The default global ordering (defined order).
-
1
class Identity
-
1
def order(items)
-
23
items
-
end
-
end
-
-
# @private
-
# Orders items randomly.
-
1
class Random
-
1
def initialize(configuration)
-
1
@configuration = configuration
-
1
@used = false
-
end
-
-
1
def used?
-
2
@used
-
end
-
-
1
def order(items)
-
@used = true
-
-
seed = @configuration.seed.to_s
-
items.sort_by { |item| jenkins_hash_digest(seed + item.id) }
-
end
-
-
1
private
-
-
# http://en.wikipedia.org/wiki/Jenkins_hash_function
-
# Jenkins provides a good distribution and is simpler than MD5.
-
# It's a bit slower than MD5 (primarily because `Digest::MD5` is
-
# implemented in C) but has the advantage of not requiring us
-
# to load another part of stdlib, which we try to minimize.
-
1
def jenkins_hash_digest(string)
-
hash = 0
-
-
string.each_byte do |byte|
-
hash += byte
-
hash &= MAX_32_BIT
-
hash += ((hash << 10) & MAX_32_BIT)
-
hash &= MAX_32_BIT
-
hash ^= hash >> 6
-
end
-
-
hash += ((hash << 3) & MAX_32_BIT)
-
hash &= MAX_32_BIT
-
hash ^= hash >> 11
-
hash += ((hash << 15) & MAX_32_BIT)
-
hash &= MAX_32_BIT
-
hash
-
end
-
-
1
MAX_32_BIT = 4_294_967_295
-
end
-
-
# @private
-
# Orders items based on a custom block.
-
1
class Custom
-
1
def initialize(callable)
-
@callable = callable
-
end
-
-
1
def order(list)
-
@callable.call(list)
-
end
-
end
-
-
# @private
-
# Stores the different ordering strategies.
-
1
class Registry
-
1
def initialize(configuration)
-
1
@configuration = configuration
-
1
@strategies = {}
-
-
1
register(:random, Random.new(configuration))
-
-
1
identity = Identity.new
-
1
register(:defined, identity)
-
-
# The default global ordering is --defined.
-
1
register(:global, identity)
-
end
-
-
1
def fetch(name, &fallback)
-
23
@strategies.fetch(name, &fallback)
-
end
-
-
1
def register(sym, strategy)
-
3
@strategies[sym] = strategy
-
end
-
-
1
def used_random_seed?
-
2
@strategies[:random].used?
-
end
-
end
-
-
# @private
-
# Manages ordering configuration.
-
#
-
# @note This is not intended to be used externally. Use
-
# the APIs provided by `RSpec::Core::Configuration` instead.
-
1
class ConfigurationManager
-
1
attr_reader :seed, :ordering_registry
-
-
1
def initialize
-
1
@ordering_registry = Registry.new(self)
-
1
@seed = rand(0xFFFF)
-
1
@seed_forced = false
-
1
@order_forced = false
-
end
-
-
1
def seed_used?
-
2
ordering_registry.used_random_seed?
-
end
-
-
1
def seed=(seed)
-
return if @seed_forced
-
register_ordering(:global, ordering_registry.fetch(:random))
-
@seed = seed.to_i
-
end
-
-
1
def order=(type)
-
order, seed = type.to_s.split(':')
-
@seed = seed.to_i if seed
-
-
ordering_name = if order.include?('rand')
-
:random
-
elsif order == 'defined'
-
:defined
-
end
-
-
register_ordering(:global, ordering_registry.fetch(ordering_name)) if ordering_name
-
end
-
-
1
def force(hash)
-
1
if hash.key?(:seed)
-
self.seed = hash[:seed]
-
@seed_forced = true
-
@order_forced = true
-
1
elsif hash.key?(:order)
-
self.order = hash[:order]
-
@order_forced = true
-
end
-
end
-
-
1
def register_ordering(name, strategy=Custom.new(Proc.new { |l| yield l }))
-
return if @order_forced && name == :global
-
ordering_registry.register(name, strategy)
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Core
-
# Provides methods to mark examples as pending. These methods are available
-
# to be called from within any example or hook.
-
1
module Pending
-
# Raised in the middle of an example to indicate that it should be marked
-
# as skipped.
-
1
class SkipDeclaredInExample < StandardError
-
1
attr_reader :argument
-
-
1
def initialize(argument)
-
@argument = argument
-
end
-
end
-
-
# If Test::Unit is loaded, we'll use its error as baseclass, so that
-
# Test::Unit will report unmet RSpec expectations as failures rather than
-
# errors.
-
1
begin
-
1
class PendingExampleFixedError < Test::Unit::AssertionFailedError; end
-
rescue
-
1
class PendingExampleFixedError < StandardError; end
-
end
-
-
# @private
-
1
NO_REASON_GIVEN = 'No reason given'
-
-
# @private
-
1
NOT_YET_IMPLEMENTED = 'Not yet implemented'
-
-
# @overload pending()
-
# @overload pending(message)
-
#
-
# Marks an example as pending. The rest of the example will still be
-
# executed, and if it passes the example will fail to indicate that the
-
# pending can be removed.
-
#
-
# @param message [String] optional message to add to the summary report.
-
#
-
# @example
-
# describe "an example" do
-
# # reported as "Pending: no reason given"
-
# it "is pending with no message" do
-
# pending
-
# raise "broken"
-
# end
-
#
-
# # reported as "Pending: something else getting finished"
-
# it "is pending with a custom message" do
-
# pending("something else getting finished")
-
# raise "broken"
-
# end
-
# end
-
#
-
# @note `before(:example)` hooks are eval'd when you use the `pending`
-
# method within an example. If you want to declare an example `pending`
-
# and bypass the `before` hooks as well, you can pass `:pending => true`
-
# to the `it` method:
-
#
-
# it "does something", :pending => true do
-
# # ...
-
# end
-
#
-
# or pass `:pending => "something else getting finished"` to add a
-
# message to the summary report:
-
#
-
# it "does something", :pending => "something else getting finished" do
-
# # ...
-
# end
-
1
def pending(message=nil)
-
current_example = RSpec.current_example
-
-
if block_given?
-
raise ArgumentError, <<-EOS.gsub(/^\s+\|/, '')
-
|The semantics of `RSpec::Core::Pending#pending` have changed in
-
|RSpec 3. In RSpec 2.x, it caused the example to be skipped. In
-
|RSpec 3, the rest of the example is still run but is expected to
-
|fail, and will be marked as a failure (rather than as pending) if
-
|the example passes.
-
|
-
|Passing a block within an example is now deprecated. Marking the
-
|example as pending provides the same behavior in RSpec 3 which was
-
|provided only by the block in RSpec 2.x.
-
|
-
|Move the code in the block provided to `pending` into the rest of
-
|the example body.
-
|
-
|Called from #{CallerFilter.first_non_rspec_line}.
-
|
-
EOS
-
elsif current_example
-
Pending.mark_pending! current_example, message
-
else
-
raise "`pending` may not be used outside of examples, such as in " \
-
"before(:context). Maybe you want `skip`?"
-
end
-
end
-
-
# @overload skip()
-
# @overload skip(message)
-
#
-
# Marks an example as pending and skips execution.
-
#
-
# @param message [String] optional message to add to the summary report.
-
#
-
# @example
-
# describe "an example" do
-
# # reported as "Pending: no reason given"
-
# it "is skipped with no message" do
-
# skip
-
# end
-
#
-
# # reported as "Pending: something else getting finished"
-
# it "is skipped with a custom message" do
-
# skip "something else getting finished"
-
# end
-
# end
-
1
def skip(message=nil)
-
current_example = RSpec.current_example
-
-
Pending.mark_skipped!(current_example, message) if current_example
-
-
raise SkipDeclaredInExample.new(message)
-
end
-
-
# @private
-
#
-
# Mark example as skipped.
-
#
-
# @param example [RSpec::Core::Example] the example to mark as skipped
-
# @param message_or_bool [Boolean, String] the message to use, or true
-
1
def self.mark_skipped!(example, message_or_bool)
-
Pending.mark_pending! example, message_or_bool
-
example.metadata[:skip] = true
-
end
-
-
# @private
-
#
-
# Mark example as pending.
-
#
-
# @param example [RSpec::Core::Example] the example to mark as pending
-
# @param message_or_bool [Boolean, String] the message to use, or true
-
1
def self.mark_pending!(example, message_or_bool)
-
message = if !message_or_bool || !(String === message_or_bool)
-
NO_REASON_GIVEN
-
else
-
message_or_bool
-
end
-
-
example.metadata[:pending] = true
-
example.execution_result.pending_message = message
-
example.execution_result.pending_fixed = false
-
end
-
-
# @private
-
#
-
# Mark example as fixed.
-
#
-
# @param example [RSpec::Core::Example] the example to mark as fixed
-
1
def self.mark_fixed!(example)
-
example.execution_result.pending_fixed = true
-
end
-
end
-
end
-
end
-
1
module RSpec::Core
-
# A reporter will send notifications to listeners, usually formatters for the
-
# spec suite run.
-
1
class Reporter
-
# @private
-
1
RSPEC_NOTIFICATIONS = Set.new(
-
[
-
:close, :deprecation, :deprecation_summary, :dump_failures, :dump_pending,
-
:dump_profile, :dump_summary, :example_failed, :example_group_finished,
-
:example_group_started, :example_passed, :example_pending, :example_started,
-
:message, :seed, :start, :start_dump, :stop, :example_finished
-
])
-
-
1
def initialize(configuration)
-
1
@configuration = configuration
-
19
@listeners = Hash.new { |h, k| h[k] = Set.new }
-
1
@examples = []
-
1
@failed_examples = []
-
1
@pending_examples = []
-
1
@duration = @start = @load_time = nil
-
end
-
-
# @private
-
1
attr_reader :examples, :failed_examples, :pending_examples
-
-
# @private
-
1
def reset
-
@examples = []
-
@failed_examples = []
-
@pending_examples = []
-
@profiler = Profiler.new if defined?(@profiler)
-
end
-
-
# @private
-
1
def setup_profiler
-
@profiler = Profiler.new
-
register_listener @profiler, *Profiler::NOTIFICATIONS
-
end
-
-
# Registers a listener to a list of notifications. The reporter will send
-
# notification of events to all registered listeners.
-
#
-
# @param listener [Object] An obect that wishes to be notified of reporter
-
# events
-
# @param notifications [Array] Array of symbols represents the events a
-
# listener wishes to subscribe too
-
1
def register_listener(listener, *notifications)
-
3
notifications.each do |notification|
-
13
@listeners[notification.to_sym] << listener
-
end
-
3
true
-
end
-
-
# @private
-
1
def registered_listeners(notification)
-
60
@listeners[notification].to_a
-
end
-
-
# @overload report(count, &block)
-
# @overload report(count, &block)
-
# @param expected_example_count [Integer] the number of examples being run
-
# @yield [Block] block yields itself for further reporting.
-
#
-
# Initializes the report run and yields itself for further reporting. The
-
# block is required, so that the reporter can manage cleaning up after the
-
# run.
-
#
-
# @example
-
#
-
# reporter.report(group.examples.size) do |r|
-
# example_groups.map {|g| g.run(r) }
-
# end
-
#
-
1
def report(expected_example_count)
-
1
start(expected_example_count)
-
1
begin
-
1
yield self
-
ensure
-
1
finish
-
end
-
end
-
-
# @private
-
1
def start(expected_example_count, time=RSpec::Core::Time.now)
-
1
@start = time
-
1
@load_time = (@start - @configuration.start_time).to_f
-
1
notify :seed, Notifications::SeedNotification.new(@configuration.seed, seed_used?)
-
1
notify :start, Notifications::StartNotification.new(expected_example_count, @load_time)
-
end
-
-
# @param message [#to_s] A message object to send to formatters
-
#
-
# Send a custom message to supporting formatters.
-
1
def message(message)
-
notify :message, Notifications::MessageNotification.new(message)
-
end
-
-
# @param event [Symbol] Name of the custom event to trigger on formatters
-
# @param options [Hash] Hash of arguments to provide via `CustomNotification`
-
#
-
# Publish a custom event to supporting registered formatters.
-
# @see RSpec::Core::Notifications::CustomNotification
-
1
def publish(event, options={})
-
if RSPEC_NOTIFICATIONS.include? event
-
raise "RSpec::Core::Reporter#publish is intended for sending custom " \
-
"events not internal RSpec ones, please rename your custom event."
-
end
-
notify event, Notifications::CustomNotification.for(options)
-
end
-
-
# @private
-
1
def example_group_started(group)
-
11
notify :example_group_started, Notifications::GroupNotification.new(group) unless group.descendant_filtered_examples.empty?
-
end
-
-
# @private
-
1
def example_group_finished(group)
-
11
notify :example_group_finished, Notifications::GroupNotification.new(group) unless group.descendant_filtered_examples.empty?
-
end
-
-
# @private
-
1
def example_started(example)
-
9
@examples << example
-
9
notify :example_started, Notifications::ExampleNotification.for(example)
-
end
-
-
# @private
-
1
def example_finished(example)
-
9
notify :example_finished, Notifications::ExampleNotification.for(example)
-
end
-
-
# @private
-
1
def example_passed(example)
-
9
notify :example_passed, Notifications::ExampleNotification.for(example)
-
end
-
-
# @private
-
1
def example_failed(example)
-
@failed_examples << example
-
notify :example_failed, Notifications::ExampleNotification.for(example)
-
end
-
-
# @private
-
1
def example_pending(example)
-
@pending_examples << example
-
notify :example_pending, Notifications::ExampleNotification.for(example)
-
end
-
-
# @private
-
1
def deprecation(hash)
-
notify :deprecation, Notifications::DeprecationNotification.from_hash(hash)
-
end
-
-
# @private
-
1
def finish
-
1
close_after do
-
1
stop
-
1
notify :start_dump, Notifications::NullNotification
-
1
notify :dump_pending, Notifications::ExamplesNotification.new(self)
-
1
notify :dump_failures, Notifications::ExamplesNotification.new(self)
-
1
notify :deprecation_summary, Notifications::NullNotification
-
1
unless mute_profile_output?
-
notify :dump_profile, Notifications::ProfileNotification.new(@duration, @examples,
-
@configuration.profile_examples,
-
@profiler.example_groups)
-
end
-
1
notify :dump_summary, Notifications::SummaryNotification.new(@duration, @examples, @failed_examples,
-
@pending_examples, @load_time)
-
1
notify :seed, Notifications::SeedNotification.new(@configuration.seed, seed_used?)
-
end
-
end
-
-
# @private
-
1
def close_after
-
1
yield
-
ensure
-
1
close
-
end
-
-
# @private
-
1
def stop
-
1
@duration = (RSpec::Core::Time.now - @start).to_f if @start
-
1
notify :stop, Notifications::ExamplesNotification.new(self)
-
end
-
-
# @private
-
1
def notify(event, notification)
-
59
registered_listeners(event).each do |formatter|
-
46
formatter.__send__(event, notification)
-
end
-
end
-
-
# @private
-
1
def abort_with(msg, exit_status)
-
message(msg)
-
close
-
exit!(exit_status)
-
end
-
-
# @private
-
1
def fail_fast_limit_met?
-
return false unless (fail_fast = @configuration.fail_fast)
-
-
if fail_fast == true
-
@failed_examples.any?
-
else
-
fail_fast <= @failed_examples.size
-
end
-
end
-
-
1
private
-
-
1
def close
-
1
notify :close, Notifications::NullNotification
-
end
-
-
1
def mute_profile_output?
-
# Don't print out profiled info if there are failures and `--fail-fast` is
-
# used, it just clutters the output.
-
1
!@configuration.profile_examples? || fail_fast_limit_met?
-
end
-
-
1
def seed_used?
-
2
@configuration.seed && @configuration.seed_used?
-
end
-
end
-
-
# @private
-
# # Used in place of a {Reporter} for situations where we don't want reporting output.
-
1
class NullReporter
-
1
def self.method_missing(*)
-
# ignore
-
end
-
1
private_class_method :method_missing
-
end
-
end
-
# This is borrowed (slightly modified) from Scott Taylor's
-
# project_path project:
-
# http://github.com/smtlaissezfaire/project_path
-
1
module RSpec
-
1
module Core
-
# @private
-
1
module RubyProject
-
1
def add_to_load_path(*dirs)
-
3
dirs.each { |dir| add_dir_to_load_path(File.join(root, dir)) }
-
end
-
-
1
def add_dir_to_load_path(dir)
-
2
$LOAD_PATH.unshift(dir) unless $LOAD_PATH.include?(dir)
-
end
-
-
1
def root
-
2
@project_root ||= determine_root
-
end
-
-
1
def determine_root
-
1
find_first_parent_containing('spec') || '.'
-
end
-
-
1
def find_first_parent_containing(dir)
-
2
ascend_until { |path| File.exist?(File.join(path, dir)) }
-
end
-
-
1
def ascend_until
-
1
fs = File::SEPARATOR
-
1
escaped_slash = "\\#{fs}"
-
1
special = "_RSPEC_ESCAPED_SLASH_"
-
1
project_path = File.expand_path(".")
-
1
parts = project_path.gsub(escaped_slash, special).squeeze(fs).split(fs).map do |x|
-
5
x.gsub(special, escaped_slash)
-
end
-
-
1
until parts.empty?
-
1
path = parts.join(fs)
-
1
path = fs if path == ""
-
1
return path if yield(path)
-
parts.pop
-
end
-
end
-
-
1
module_function :add_to_load_path
-
1
module_function :add_dir_to_load_path
-
1
module_function :root
-
1
module_function :determine_root
-
1
module_function :find_first_parent_containing
-
1
module_function :ascend_until
-
end
-
end
-
end
-
1
module RSpec
-
1
module Core
-
# Provides the main entry point to run a suite of RSpec examples.
-
1
class Runner
-
# @attr_reader
-
# @private
-
1
attr_reader :options, :configuration, :world
-
-
# Register an `at_exit` hook that runs the suite when the process exits.
-
#
-
# @note This is not generally needed. The `rspec` command takes care
-
# of running examples for you without involving an `at_exit`
-
# hook. This is only needed if you are running specs using
-
# the `ruby` command, and even then, the normal way to invoke
-
# this is by requiring `rspec/autorun`.
-
1
def self.autorun
-
if autorun_disabled?
-
RSpec.deprecate("Requiring `rspec/autorun` when running RSpec via the `rspec` command")
-
return
-
elsif installed_at_exit? || running_in_drb?
-
return
-
end
-
-
at_exit { perform_at_exit }
-
@installed_at_exit = true
-
end
-
-
# @private
-
1
def self.perform_at_exit
-
# Don't bother running any specs and just let the program terminate
-
# if we got here due to an unrescued exception (anything other than
-
# SystemExit, which is raised when somebody calls Kernel#exit).
-
return unless $!.nil? || $!.is_a?(SystemExit)
-
-
# We got here because either the end of the program was reached or
-
# somebody called Kernel#exit. Run the specs and then override any
-
# existing exit status with RSpec's exit status if any specs failed.
-
invoke
-
end
-
-
# Runs the suite of specs and exits the process with an appropriate exit
-
# code.
-
1
def self.invoke
-
1
disable_autorun!
-
1
status = run(ARGV, $stderr, $stdout).to_i
-
1
exit(status) if status != 0
-
end
-
-
# Run a suite of RSpec examples. Does not exit.
-
#
-
# This is used internally by RSpec to run a suite, but is available
-
# for use by any other automation tool.
-
#
-
# If you want to run this multiple times in the same process, and you
-
# want files like `spec_helper.rb` to be reloaded, be sure to load `load`
-
# instead of `require`.
-
#
-
# @param args [Array] command-line-supported arguments
-
# @param err [IO] error stream
-
# @param out [IO] output stream
-
# @return [Fixnum] exit status code. 0 if all specs passed,
-
# or the configured failure exit code (1 by default) if specs
-
# failed.
-
1
def self.run(args, err=$stderr, out=$stdout)
-
1
trap_interrupt
-
1
options = ConfigurationOptions.new(args)
-
-
1
if options.options[:drb]
-
require 'rspec/core/drb'
-
begin
-
DRbRunner.new(options).run(err, out)
-
return
-
rescue DRb::DRbConnError
-
err.puts "No DRb server is running. Running in local process instead ..."
-
end
-
end
-
-
1
new(options).run(err, out)
-
end
-
-
1
def initialize(options, configuration=RSpec.configuration, world=RSpec.world)
-
1
@options = options
-
1
@configuration = configuration
-
1
@world = world
-
end
-
-
# Configures and runs a spec suite.
-
#
-
# @param err [IO] error stream
-
# @param out [IO] output stream
-
1
def run(err, out)
-
1
setup(err, out)
-
1
run_specs(@world.ordered_example_groups).tap do
-
1
persist_example_statuses
-
end
-
end
-
-
# Wires together the various configuration objects and state holders.
-
#
-
# @param err [IO] error stream
-
# @param out [IO] output stream
-
1
def setup(err, out)
-
1
@configuration.error_stream = err
-
1
@configuration.output_stream = out if @configuration.output_stream == $stdout
-
1
@options.configure(@configuration)
-
1
@configuration.load_spec_files
-
1
@world.announce_filters
-
end
-
-
# Runs the provided example groups.
-
#
-
# @param example_groups [Array<RSpec::Core::ExampleGroup>] groups to run
-
# @return [Fixnum] exit status code. 0 if all specs passed,
-
# or the configured failure exit code (1 by default) if specs
-
# failed.
-
1
def run_specs(example_groups)
-
1
@configuration.reporter.report(@world.example_count(example_groups)) do |reporter|
-
1
@configuration.with_suite_hooks do
-
2
example_groups.map { |g| g.run(reporter) }.all? ? 0 : @configuration.failure_exit_code
-
end
-
end
-
end
-
-
1
private
-
-
1
def persist_example_statuses
-
1
return unless (path = @configuration.example_status_persistence_file_path)
-
-
ExampleStatusPersister.persist(@world.all_examples, path)
-
rescue SystemCallError => e
-
RSpec.warning "Could not write example statuses to #{path} (configured as " \
-
"`config.example_status_persistence_file_path`) due to a " \
-
"system error: #{e.inspect}. Please check that the config " \
-
"option is set to an accessible, valid file path", :call_site => nil
-
end
-
-
# @private
-
1
def self.disable_autorun!
-
1
@autorun_disabled = true
-
end
-
-
# @private
-
1
def self.autorun_disabled?
-
@autorun_disabled ||= false
-
end
-
-
# @private
-
1
def self.installed_at_exit?
-
@installed_at_exit ||= false
-
end
-
-
# @private
-
# rubocop:disable Lint/EnsureReturn
-
1
def self.running_in_drb?
-
if defined?(DRb) && DRb.current_server
-
require 'socket'
-
require 'uri'
-
local_ipv4 = IPSocket.getaddress(Socket.gethostname)
-
local_drb = ["127.0.0.1", "localhost", local_ipv4].any? { |addr| addr == URI(DRb.current_server.uri).host }
-
end
-
rescue DRb::DRbServerNotFound
-
ensure
-
return local_drb || false
-
end
-
# rubocop:enable Lint/EnsureReturn
-
-
# @private
-
1
def self.trap_interrupt
-
1
trap('INT') { handle_interrupt }
-
end
-
-
# @private
-
1
def self.handle_interrupt
-
if RSpec.world.wants_to_quit
-
exit!(1)
-
else
-
RSpec.world.wants_to_quit = true
-
STDERR.puts "\nRSpec is shutting down and will print the summary report... Interrupt again to force quit."
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Core
-
# @private
-
#
-
# We use this to replace `::Set` so we can have the advantage of
-
# constant time key lookups for unique arrays but without the
-
# potential to pollute a developers environment with an extra
-
# piece of the stdlib. This helps to prevent false positive
-
# builds.
-
#
-
1
class Set
-
1
include Enumerable
-
-
1
def initialize(array=[])
-
54
@values = {}
-
54
merge(array)
-
end
-
-
1
def empty?
-
@values.empty?
-
end
-
-
1
def <<(key)
-
14
@values[key] = true
-
14
self
-
end
-
-
1
def delete(key)
-
@values.delete(key)
-
end
-
-
1
def each(&block)
-
144
@values.keys.each(&block)
-
144
self
-
end
-
-
1
def include?(key)
-
9
@values.key?(key)
-
end
-
-
1
def merge(values)
-
80
values.each do |key|
-
45
@values[key] = true
-
end
-
80
self
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Core
-
# Represents some functionality that is shared with multiple example groups.
-
# The functionality is defined by the provided block, which is lazily
-
# eval'd when the `SharedExampleGroupModule` instance is included in an example
-
# group.
-
1
class SharedExampleGroupModule < Module
-
1
def initialize(description, definition)
-
@description = description
-
@definition = definition
-
end
-
-
# Provides a human-readable representation of this module.
-
1
def inspect
-
"#<#{self.class.name} #{@description.inspect}>"
-
end
-
1
alias to_s inspect
-
-
# Ruby callback for when a module is included in another module is class.
-
# Our definition evaluates the shared group block in the context of the
-
# including example group.
-
1
def included(klass)
-
inclusion_line = klass.metadata[:location]
-
SharedExampleGroupInclusionStackFrame.with_frame(@description, inclusion_line) do
-
klass.class_exec(&@definition)
-
end
-
end
-
end
-
-
# Shared example groups let you define common context and/or common
-
# examples that you wish to use in multiple example groups.
-
#
-
# When defined, the shared group block is stored for later evaluation.
-
# It can later be included in an example group either explicitly
-
# (using `include_examples`, `include_context` or `it_behaves_like`)
-
# or implicitly (via matching metadata).
-
#
-
# Named shared example groups are scoped based on where they are
-
# defined. Shared groups defined in an example group are available
-
# for inclusion in that example group or any child example groups,
-
# but not in any parent or sibling example groups. Shared example
-
# groups defined at the top level can be included from any example group.
-
1
module SharedExampleGroup
-
# @overload shared_examples(name, &block)
-
# @param name [String, Symbol, Module] identifer to use when looking up
-
# this shared group
-
# @param block The block to be eval'd
-
# @overload shared_examples(name, metadata, &block)
-
# @param name [String, Symbol, Module] identifer to use when looking up
-
# this shared group
-
# @param metadata [Array<Symbol>, Hash] metadata to attach to this
-
# group; any example group or example with matching metadata will
-
# automatically include this shared example group.
-
# @param block The block to be eval'd
-
# @overload shared_examples(metadata, &block)
-
# @param metadata [Array<Symbol>, Hash] metadata to attach to this
-
# group; any example group or example with matching metadata will
-
# automatically include this shared example group.
-
# @param block The block to be eval'd
-
#
-
# Stores the block for later use. The block will be evaluated
-
# in the context of an example group via `include_examples`,
-
# `include_context`, or `it_behaves_like`.
-
#
-
# @example
-
# shared_examples "auditable" do
-
# it "stores an audit record on save!" do
-
# expect { auditable.save! }.to change(Audit, :count).by(1)
-
# end
-
# end
-
#
-
# describe Account do
-
# it_behaves_like "auditable" do
-
# let(:auditable) { Account.new }
-
# end
-
# end
-
#
-
# @see ExampleGroup.it_behaves_like
-
# @see ExampleGroup.include_examples
-
# @see ExampleGroup.include_context
-
1
def shared_examples(name, *args, &block)
-
top_level = self == ExampleGroup
-
if top_level && RSpec::Support.thread_local_data[:in_example_group]
-
raise "Creating isolated shared examples from within a context is " \
-
"not allowed. Remove `RSpec.` prefix or move this to a " \
-
"top-level scope."
-
end
-
-
RSpec.world.shared_example_group_registry.add(self, name, *args, &block)
-
end
-
1
alias shared_context shared_examples
-
1
alias shared_examples_for shared_examples
-
-
# @api private
-
#
-
# Shared examples top level DSL.
-
1
module TopLevelDSL
-
# @private
-
# rubocop:disable Lint/NestedMethodDefinition
-
1
def self.definitions
-
2
proc do
-
3
def shared_examples(name, *args, &block)
-
RSpec.world.shared_example_group_registry.add(:main, name, *args, &block)
-
end
-
3
alias shared_context shared_examples
-
3
alias shared_examples_for shared_examples
-
end
-
end
-
# rubocop:enable Lint/NestedMethodDefinition
-
-
# @private
-
1
def self.exposed_globally?
-
1
@exposed_globally ||= false
-
end
-
-
# @api private
-
#
-
# Adds the top level DSL methods to Module and the top level binding.
-
1
def self.expose_globally!
-
1
return if exposed_globally?
-
1
Core::DSL.change_global_dsl(&definitions)
-
1
@exposed_globally = true
-
end
-
-
# @api private
-
#
-
# Removes the top level DSL methods to Module and the top level binding.
-
1
def self.remove_globally!
-
return unless exposed_globally?
-
-
Core::DSL.change_global_dsl do
-
undef shared_examples
-
undef shared_context
-
undef shared_examples_for
-
end
-
-
@exposed_globally = false
-
end
-
end
-
-
# @private
-
1
class Registry
-
1
def add(context, name, *metadata_args, &block)
-
ensure_block_has_source_location(block) { CallerFilter.first_non_rspec_line }
-
-
if valid_name?(name)
-
warn_if_key_taken context, name, block
-
shared_example_groups[context][name] = block
-
else
-
metadata_args.unshift name
-
end
-
-
return if metadata_args.empty?
-
RSpec.configuration.include SharedExampleGroupModule.new(name, block), *metadata_args
-
end
-
-
1
def find(lookup_contexts, name)
-
lookup_contexts.each do |context|
-
found = shared_example_groups[context][name]
-
return found if found
-
end
-
-
shared_example_groups[:main][name]
-
end
-
-
1
private
-
-
1
def shared_example_groups
-
@shared_example_groups ||= Hash.new { |hash, context| hash[context] = {} }
-
end
-
-
1
def valid_name?(candidate)
-
case candidate
-
when String, Symbol, Module then true
-
else false
-
end
-
end
-
-
1
def warn_if_key_taken(context, key, new_block)
-
existing_block = shared_example_groups[context][key]
-
-
return unless existing_block
-
-
RSpec.warn_with <<-WARNING.gsub(/^ +\|/, ''), :call_site => nil
-
|WARNING: Shared example group '#{key}' has been previously defined at:
-
| #{formatted_location existing_block}
-
|...and you are now defining it at:
-
| #{formatted_location new_block}
-
|The new definition will overwrite the original one.
-
WARNING
-
end
-
-
1
def formatted_location(block)
-
block.source_location.join ":"
-
end
-
-
1
if Proc.method_defined?(:source_location)
-
1
def ensure_block_has_source_location(_block); end
-
else # for 1.8.7
-
# :nocov:
-
skipped
def ensure_block_has_source_location(block)
-
skipped
source_location = yield.split(':')
-
skipped
block.extend Module.new { define_method(:source_location) { source_location } }
-
skipped
end
-
# :nocov:
-
end
-
end
-
end
-
end
-
-
1
instance_exec(&Core::SharedExampleGroup::TopLevelDSL.definitions)
-
end
-
1
module RSpec
-
1
module Core
-
# @private
-
# Deals with the fact that `shellwords` only works on POSIX systems.
-
1
module ShellEscape
-
1
module_function
-
-
1
def quote(argument)
-
"'#{argument.gsub("'", "\\\\'")}'"
-
end
-
-
1
if RSpec::Support::OS.windows?
-
# :nocov:
-
skipped
alias escape quote
-
# :nocov:
-
else
-
1
require 'shellwords'
-
-
1
def escape(shell_command)
-
shell_command.shellescape
-
end
-
end
-
-
# Known shells that require quoting: zsh, csh, tcsh.
-
#
-
# Feel free to add other shells to this list that are known to
-
# allow `rspec ./some_spec.rb[1:1]` syntax without quoting the id.
-
#
-
# @private
-
1
SHELLS_ALLOWING_UNQUOTED_IDS = %w[ bash ksh fish ]
-
-
1
def conditionally_quote(id)
-
return id if shell_allows_unquoted_ids?
-
quote(id)
-
end
-
-
1
def shell_allows_unquoted_ids?
-
# Note: ENV['SHELL'] isn't necessarily the shell the user is currently running.
-
# According to http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html:
-
# "This variable shall represent a pathname of the user's preferred command language interpreter."
-
#
-
# It's the best we can easily do, though. We err on the side of safety (quoting
-
# the id when not actually needed) so it's not a big deal if the user is actually
-
# using a different shell.
-
SHELLS_ALLOWING_UNQUOTED_IDS.include?(ENV['SHELL'].to_s.split('/').last)
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_core 'source/node'
-
1
RSpec::Support.require_rspec_core 'source/syntax_highlighter'
-
1
RSpec::Support.require_rspec_core 'source/token'
-
-
1
module RSpec
-
1
module Core
-
# @private
-
# Represents a Ruby source file and provides access to AST and tokens.
-
1
class Source
-
1
attr_reader :source, :path
-
-
1
def self.from_file(path)
-
source = File.read(path)
-
new(source, path)
-
end
-
-
1
def initialize(source_string, path=nil)
-
@source = source_string
-
@path = path ? File.expand_path(path) : '(string)'
-
end
-
-
1
def lines
-
@lines ||= source.split("\n")
-
end
-
-
1
def ast
-
@ast ||= begin
-
require 'ripper'
-
sexp = Ripper.sexp(source)
-
raise SyntaxError unless sexp
-
Node.new(sexp)
-
end
-
end
-
-
1
def tokens
-
@tokens ||= begin
-
require 'ripper'
-
tokens = Ripper.lex(source)
-
Token.tokens_from_ripper_tokens(tokens)
-
end
-
end
-
-
1
def nodes_by_line_number
-
@nodes_by_line_number ||= begin
-
nodes_by_line_number = ast.select(&:location).group_by { |node| node.location.line }
-
Hash.new { |hash, key| hash[key] = [] }.merge(nodes_by_line_number)
-
end
-
end
-
-
1
def tokens_by_line_number
-
@tokens_by_line_number ||= begin
-
nodes_by_line_number = tokens.group_by { |token| token.location.line }
-
Hash.new { |hash, key| hash[key] = [] }.merge(nodes_by_line_number)
-
end
-
end
-
-
1
def inspect
-
"#<#{self.class} #{path}>"
-
end
-
-
# @private
-
1
class Cache
-
1
attr_reader :syntax_highlighter
-
-
1
def initialize(configuration)
-
@sources_by_path = {}
-
@syntax_highlighter = SyntaxHighlighter.new(configuration)
-
end
-
-
1
def source_from_file(path)
-
@sources_by_path[path] ||= Source.from_file(path)
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Core
-
1
class Source
-
# @private
-
# Represents a source location of node or token.
-
1
Location = Struct.new(:line, :column) do
-
1
def self.location?(array)
-
array.is_a?(Array) && array.size == 2 && array.all? { |e| e.is_a?(Integer) }
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_core "source/location"
-
-
1
module RSpec
-
1
module Core
-
1
class Source
-
# @private
-
# A wrapper for Ripper AST node which is generated with `Ripper.sexp`.
-
1
class Node
-
1
include Enumerable
-
-
1
attr_reader :sexp, :parent
-
-
1
def self.sexp?(array)
-
array.is_a?(Array) && array.first.is_a?(Symbol)
-
end
-
-
1
def initialize(ripper_sexp, parent=nil)
-
@sexp = ripper_sexp.freeze
-
@parent = parent
-
end
-
-
1
def type
-
sexp[0]
-
end
-
-
1
def args
-
@args ||= raw_args.map do |raw_arg|
-
if Node.sexp?(raw_arg)
-
Node.new(raw_arg, self)
-
elsif Location.location?(raw_arg)
-
Location.new(*raw_arg)
-
elsif raw_arg.is_a?(Array)
-
GroupNode.new(raw_arg, self)
-
else
-
raw_arg
-
end
-
end.freeze
-
end
-
-
1
def children
-
@children ||= args.select { |arg| arg.is_a?(Node) }.freeze
-
end
-
-
1
def location
-
@location ||= args.find { |arg| arg.is_a?(Location) }
-
end
-
-
1
def each(&block)
-
return to_enum(__method__) unless block_given?
-
-
yield self
-
-
children.each do |child|
-
child.each(&block)
-
end
-
end
-
-
1
def each_ancestor
-
return to_enum(__method__) unless block_given?
-
-
current_node = self
-
-
while (current_node = current_node.parent)
-
yield current_node
-
end
-
end
-
-
1
def inspect
-
"#<#{self.class} #{type}>"
-
end
-
-
1
private
-
-
1
def raw_args
-
sexp[1..-1] || []
-
end
-
end
-
-
# @private
-
1
class GroupNode < Node
-
1
def type
-
:group
-
end
-
-
1
private
-
-
1
def raw_args
-
sexp
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Core
-
1
class Source
-
# @private
-
# Provides terminal syntax highlighting of code snippets
-
# when coderay is available.
-
1
class SyntaxHighlighter
-
1
def initialize(configuration)
-
@configuration = configuration
-
end
-
-
1
def highlight(lines)
-
implementation.highlight_syntax(lines)
-
end
-
-
1
private
-
-
1
if RSpec::Support::OS.windows?
-
# :nocov:
-
skipped
def implementation
-
skipped
WindowsImplementation
-
skipped
end
-
# :nocov:
-
else
-
1
def implementation
-
return color_enabled_implementation if @configuration.color_enabled?
-
NoSyntaxHighlightingImplementation
-
end
-
end
-
-
1
def color_enabled_implementation
-
@color_enabled_implementation ||= begin
-
require 'coderay'
-
CodeRayImplementation
-
rescue LoadError
-
NoSyntaxHighlightingImplementation
-
end
-
end
-
-
# @private
-
1
module CodeRayImplementation
-
1
RESET_CODE = "\e[0m"
-
-
1
def self.highlight_syntax(lines)
-
highlighted = begin
-
CodeRay.encode(lines.join("\n"), :ruby, :terminal)
-
rescue Support::AllExceptionsExceptOnesWeMustNotRescue
-
return lines
-
end
-
-
highlighted.split("\n").map do |line|
-
line.sub(/\S/) { |char| char.insert(0, RESET_CODE) }
-
end
-
end
-
end
-
-
# @private
-
1
module NoSyntaxHighlightingImplementation
-
1
def self.highlight_syntax(lines)
-
lines
-
end
-
end
-
-
# @private
-
# Not sure why, but our code above (and/or coderay itself) does not work
-
# on Windows, so we disable the feature on Windows.
-
1
WindowsImplementation = NoSyntaxHighlightingImplementation
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_core "source/location"
-
-
1
module RSpec
-
1
module Core
-
1
class Source
-
# @private
-
# A wrapper for Ripper token which is generated with `Ripper.lex`.
-
1
class Token
-
1
CLOSING_TYPES_BY_OPENING_TYPE = {
-
:on_lbracket => :on_rbracket,
-
:on_lparen => :on_rparen,
-
:on_lbrace => :on_rbrace,
-
:on_heredoc_beg => :on_heredoc_end
-
}.freeze
-
-
1
CLOSING_KEYWORDS_BY_OPENING_KEYWORD = {
-
'do' => 'end'
-
}.freeze
-
-
1
attr_reader :token
-
-
1
def self.tokens_from_ripper_tokens(ripper_tokens)
-
ripper_tokens.map { |ripper_token| new(ripper_token) }.freeze
-
end
-
-
1
def initialize(ripper_token)
-
@token = ripper_token.freeze
-
end
-
-
1
def location
-
@location ||= Location.new(*token[0])
-
end
-
-
1
def type
-
token[1]
-
end
-
-
1
def string
-
token[2]
-
end
-
-
1
def ==(other)
-
token == other.token
-
end
-
-
1
alias_method :eql?, :==
-
-
1
def inspect
-
"#<#{self.class} #{type} #{string.inspect}>"
-
end
-
-
1
def keyword?
-
type == :on_kw
-
end
-
-
1
def opening?
-
opening_delimiter? || opening_keyword?
-
end
-
-
1
def closed_by?(other)
-
closed_by_delimiter?(other) || closed_by_keyword?(other)
-
end
-
-
1
private
-
-
1
def opening_delimiter?
-
CLOSING_TYPES_BY_OPENING_TYPE.key?(type)
-
end
-
-
1
def opening_keyword?
-
return false unless keyword?
-
CLOSING_KEYWORDS_BY_OPENING_KEYWORD.key?(string)
-
end
-
-
1
def closed_by_delimiter?(other)
-
other.type == CLOSING_TYPES_BY_OPENING_TYPE[type]
-
end
-
-
1
def closed_by_keyword?(other)
-
return false unless other.keyword?
-
other.string == CLOSING_KEYWORDS_BY_OPENING_KEYWORD[string]
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Core
-
# Version information for RSpec Core.
-
1
module Version
-
# Current version of RSpec Core, in semantic versioning format.
-
1
STRING = '3.4.4'
-
end
-
end
-
end
-
1
require "rspec/support/warnings"
-
-
1
module RSpec
-
1
module Core
-
# @private
-
1
module Warnings
-
# @private
-
#
-
# Used internally to print deprecation warnings.
-
1
def deprecate(deprecated, data={})
-
RSpec.configuration.reporter.deprecation(
-
{
-
:deprecated => deprecated,
-
:call_site => CallerFilter.first_non_rspec_line
-
}.merge(data)
-
)
-
end
-
-
# @private
-
#
-
# Used internally to print deprecation warnings.
-
1
def warn_deprecation(message, opts={})
-
RSpec.configuration.reporter.deprecation opts.merge(:message => message)
-
end
-
-
# @private
-
1
def warn_with(message, options={})
-
if options[:use_spec_location_as_call_site]
-
message += "." unless message.end_with?(".")
-
-
if RSpec.current_example
-
message += " Warning generated from spec at `#{RSpec.current_example.location}`."
-
end
-
end
-
-
super(message, options)
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Core
-
# @api private
-
#
-
# Internal container for global non-configuration data.
-
1
class World
-
# @private
-
1
attr_reader :example_groups, :filtered_examples
-
-
# Used internally to determine what to do when a SIGINT is received.
-
1
attr_accessor :wants_to_quit
-
-
1
def initialize(configuration=RSpec.configuration)
-
1
@configuration = configuration
-
1
@example_groups = []
-
1
@example_group_counts_by_spec_file = Hash.new(0)
-
1
@filtered_examples = Hash.new do |hash, group|
-
11
hash[group] = filter_manager.prune(group.examples)
-
end
-
end
-
-
# @api private
-
#
-
# Apply ordering strategy from configuration to example groups.
-
1
def ordered_example_groups
-
1
ordering_strategy = @configuration.ordering_registry.fetch(:global)
-
1
ordering_strategy.order(@example_groups)
-
end
-
-
# @api private
-
#
-
# Reset world to 'scratch' before running suite.
-
1
def reset
-
example_groups.clear
-
@shared_example_group_registry = nil
-
end
-
-
# @private
-
1
def filter_manager
-
12
@configuration.filter_manager
-
end
-
-
# @private
-
1
def registered_example_group_files
-
1
@example_group_counts_by_spec_file.keys
-
end
-
-
# @api private
-
#
-
# Register an example group.
-
1
def register(example_group)
-
1
@configuration.on_example_group_definition_callbacks.each { |block| block.call(example_group) }
-
1
example_groups << example_group
-
1
@example_group_counts_by_spec_file[example_group.metadata[:absolute_file_path]] += 1
-
1
example_group
-
end
-
-
# @private
-
1
def num_example_groups_defined_in(file)
-
1
@example_group_counts_by_spec_file[file]
-
end
-
-
# @private
-
1
def shared_example_group_registry
-
@shared_example_group_registry ||= SharedExampleGroup::Registry.new
-
end
-
-
# @private
-
1
def inclusion_filter
-
1
@configuration.inclusion_filter
-
end
-
-
# @private
-
1
def exclusion_filter
-
1
@configuration.exclusion_filter
-
end
-
-
# @api private
-
#
-
# Get count of examples to be run.
-
1
def example_count(groups=example_groups)
-
2
FlatMap.flat_map(groups) { |g| g.descendants }.
-
24
inject(0) { |a, e| a + e.filtered_examples.size }
-
end
-
-
# @private
-
1
def all_example_groups
-
1
FlatMap.flat_map(example_groups) { |g| g.descendants }
-
end
-
-
# @private
-
1
def all_examples
-
FlatMap.flat_map(all_example_groups) { |g| g.examples }
-
end
-
-
# @api private
-
#
-
# Find line number of previous declaration.
-
1
def preceding_declaration_line(absolute_file_name, filter_line)
-
line_numbers = descending_declaration_line_numbers_by_file.fetch(absolute_file_name) do
-
return nil
-
end
-
-
line_numbers.find { |num| num <= filter_line }
-
end
-
-
# @private
-
1
def reporter
-
@configuration.reporter
-
end
-
-
# @private
-
1
def source_cache
-
@source_cache ||= begin
-
RSpec::Support.require_rspec_core "source"
-
Source::Cache.new(@configuration)
-
end
-
end
-
-
# @api private
-
#
-
# Notify reporter of filters.
-
1
def announce_filters
-
1
fail_if_config_and_cli_options_invalid
-
1
filter_announcements = []
-
-
1
announce_inclusion_filter filter_announcements
-
1
announce_exclusion_filter filter_announcements
-
-
1
unless filter_manager.empty?
-
if filter_announcements.length == 1
-
report_filter_message("Run options: #{filter_announcements[0]}")
-
else
-
report_filter_message("Run options:\n #{filter_announcements.join("\n ")}")
-
end
-
end
-
-
1
if @configuration.run_all_when_everything_filtered? && example_count.zero? && !@configuration.only_failures?
-
report_filter_message("#{everything_filtered_message}; ignoring #{inclusion_filter.description}")
-
filtered_examples.clear
-
inclusion_filter.clear
-
end
-
-
1
return unless example_count.zero?
-
-
example_groups.clear
-
if filter_manager.empty?
-
report_filter_message("No examples found.")
-
elsif exclusion_filter.empty? || inclusion_filter.empty?
-
report_filter_message(everything_filtered_message)
-
end
-
end
-
-
# @private
-
1
def report_filter_message(message)
-
reporter.message(message) unless @configuration.silence_filter_announcements?
-
end
-
-
# @private
-
1
def everything_filtered_message
-
"\nAll examples were filtered out"
-
end
-
-
# @api private
-
#
-
# Add inclusion filters to announcement message.
-
1
def announce_inclusion_filter(announcements)
-
1
return if inclusion_filter.empty?
-
-
announcements << "include #{inclusion_filter.description}"
-
end
-
-
# @api private
-
#
-
# Add exclusion filters to announcement message.
-
1
def announce_exclusion_filter(announcements)
-
1
return if exclusion_filter.empty?
-
-
announcements << "exclude #{exclusion_filter.description}"
-
end
-
-
1
private
-
-
1
def descending_declaration_line_numbers_by_file
-
@descending_declaration_line_numbers_by_file ||= begin
-
declaration_locations = FlatMap.flat_map(example_groups, &:declaration_locations)
-
hash_of_arrays = Hash.new { |h, k| h[k] = [] }
-
-
# TODO: change `inject` to `each_with_object` when we drop 1.8.7 support.
-
line_nums_by_file = declaration_locations.inject(hash_of_arrays) do |hash, (file_name, line_number)|
-
hash[file_name] << line_number
-
hash
-
end
-
-
line_nums_by_file.each_value do |list|
-
list.sort!
-
list.reverse!
-
end
-
end
-
end
-
-
1
def fail_if_config_and_cli_options_invalid
-
1
return unless @configuration.only_failures_but_not_configured?
-
-
reporter.abort_with(
-
"\nTo use `--only-failures`, you must first set " \
-
"`config.example_status_persistence_file_path`.",
-
1 # exit code
-
)
-
end
-
end
-
end
-
end
-
1
require 'rspec/support'
-
1
RSpec::Support.require_rspec_support "caller_filter"
-
1
RSpec::Support.require_rspec_support "warnings"
-
1
RSpec::Support.require_rspec_support "object_formatter"
-
-
1
require 'rspec/matchers'
-
-
7
RSpec::Support.define_optimized_require_for_rspec(:expectations) { |f| require_relative(f) }
-
-
%w[
-
expectation_target
-
configuration
-
fail_with
-
handler
-
version
-
6
].each { |file| RSpec::Support.require_rspec_expectations(file) }
-
-
1
module RSpec
-
# RSpec::Expectations provides a simple, readable API to express
-
# the expected outcomes in a code example. To express an expected
-
# outcome, wrap an object or block in `expect`, call `to` or `to_not`
-
# (aliased as `not_to`) and pass it a matcher object:
-
#
-
# expect(order.total).to eq(Money.new(5.55, :USD))
-
# expect(list).to include(user)
-
# expect(message).not_to match(/foo/)
-
# expect { do_something }.to raise_error
-
#
-
# The last form (the block form) is needed to match against ruby constructs
-
# that are not objects, but can only be observed when executing a block
-
# of code. This includes raising errors, throwing symbols, yielding,
-
# and changing values.
-
#
-
# When `expect(...).to` is invoked with a matcher, it turns around
-
# and calls `matcher.matches?(<object wrapped by expect>)`. For example,
-
# in the expression:
-
#
-
# expect(order.total).to eq(Money.new(5.55, :USD))
-
#
-
# ...`eq(Money.new(5.55, :USD))` returns a matcher object, and it results
-
# in the equivalent of `eq.matches?(order.total)`. If `matches?` returns
-
# `true`, the expectation is met and execution continues. If `false`, then
-
# the spec fails with the message returned by `eq.failure_message`.
-
#
-
# Given the expression:
-
#
-
# expect(order.entries).not_to include(entry)
-
#
-
# ...the `not_to` method (also available as `to_not`) invokes the equivalent of
-
# `include.matches?(order.entries)`, but it interprets `false` as success, and
-
# `true` as a failure, using the message generated by
-
# `include.failure_message_when_negated`.
-
#
-
# rspec-expectations ships with a standard set of useful matchers, and writing
-
# your own matchers is quite simple.
-
#
-
# See [RSpec::Matchers](../RSpec/Matchers) for more information about the
-
# built-in matchers that ship with rspec-expectations, and how to write your
-
# own custom matchers.
-
1
module Expectations
-
# Exception raised when an expectation fails.
-
#
-
# @note We subclass Exception so that in a stub implementation if
-
# the user sets an expectation, it can't be caught in their
-
# code by a bare `rescue`.
-
# @api public
-
1
class ExpectationNotMetError < Exception
-
end
-
-
# Exception raised from `aggregate_failures` when multiple expectations fail.
-
#
-
# @note The constant is defined here but the extensive logic of this class
-
# is lazily defined when `FailureAggregator` is autoloaded, since we do
-
# not need to waste time defining that functionality unless
-
# `aggregate_failures` is used.
-
1
class MultipleExpectationsNotMetError < ExpectationNotMetError
-
end
-
-
1
autoload :FailureAggregator, "rspec/expectations/failure_aggregator"
-
end
-
end
-
1
RSpec::Support.require_rspec_expectations "syntax"
-
-
1
module RSpec
-
1
module Expectations
-
# Provides configuration options for rspec-expectations.
-
# If you are using rspec-core, you can access this via a
-
# block passed to `RSpec::Core::Configuration#expect_with`.
-
# Otherwise, you can access it via RSpec::Expectations.configuration.
-
#
-
# @example
-
# RSpec.configure do |rspec|
-
# rspec.expect_with :rspec do |c|
-
# # c is the config object
-
# end
-
# end
-
#
-
# # or
-
#
-
# RSpec::Expectations.configuration
-
1
class Configuration
-
1
def initialize
-
1
@warn_about_potential_false_positives = true
-
end
-
-
# Configures the supported syntax.
-
# @param [Array<Symbol>, Symbol] values the syntaxes to enable
-
# @example
-
# RSpec.configure do |rspec|
-
# rspec.expect_with :rspec do |c|
-
# c.syntax = :should
-
# # or
-
# c.syntax = :expect
-
# # or
-
# c.syntax = [:should, :expect]
-
# end
-
# end
-
1
def syntax=(values)
-
1
if Array(values).include?(:expect)
-
1
Expectations::Syntax.enable_expect
-
else
-
Expectations::Syntax.disable_expect
-
end
-
-
1
if Array(values).include?(:should)
-
1
Expectations::Syntax.enable_should
-
else
-
Expectations::Syntax.disable_should
-
end
-
end
-
-
# The list of configured syntaxes.
-
# @return [Array<Symbol>] the list of configured syntaxes.
-
# @example
-
# unless RSpec::Matchers.configuration.syntax.include?(:expect)
-
# raise "this RSpec extension gem requires the rspec-expectations `:expect` syntax"
-
# end
-
1
def syntax
-
syntaxes = []
-
syntaxes << :should if Expectations::Syntax.should_enabled?
-
syntaxes << :expect if Expectations::Syntax.expect_enabled?
-
syntaxes
-
end
-
-
1
if ::RSpec.respond_to?(:configuration)
-
1
def color?
-
::RSpec.configuration.color_enabled?
-
end
-
else
-
# Indicates whether or not diffs should be colored.
-
# Delegates to rspec-core's color option if rspec-core
-
# is loaded; otherwise you can set it here.
-
attr_writer :color
-
-
# Indicates whether or not diffs should be colored.
-
# Delegates to rspec-core's color option if rspec-core
-
# is loaded; otherwise you can set it here.
-
def color?
-
defined?(@color) && @color
-
end
-
end
-
-
# Adds `should` and `should_not` to the given classes
-
# or modules. This can be used to ensure `should` works
-
# properly on things like proxy objects (particular
-
# `Delegator`-subclassed objects on 1.8).
-
#
-
# @param [Array<Module>] modules the list of classes or modules
-
# to add `should` and `should_not` to.
-
1
def add_should_and_should_not_to(*modules)
-
modules.each do |mod|
-
Expectations::Syntax.enable_should(mod)
-
end
-
end
-
-
# Sets or gets the backtrace formatter. The backtrace formatter should
-
# implement `#format_backtrace(Array<String>)`. This is used
-
# to format backtraces of errors handled by the `raise_error`
-
# matcher.
-
#
-
# If you are using rspec-core, rspec-core's backtrace formatting
-
# will be used (including respecting the presence or absence of
-
# the `--backtrace` option).
-
#
-
# @!attribute [rw] backtrace_formatter
-
1
attr_writer :backtrace_formatter
-
1
def backtrace_formatter
-
@backtrace_formatter ||= if defined?(::RSpec.configuration.backtrace_formatter)
-
::RSpec.configuration.backtrace_formatter
-
else
-
NullBacktraceFormatter
-
end
-
end
-
-
# Sets if custom matcher descriptions and failure messages
-
# should include clauses from methods defined using `chain`.
-
# @param value [Boolean]
-
1
attr_writer :include_chain_clauses_in_custom_matcher_descriptions
-
-
# Indicates whether or not custom matcher descriptions and failure messages
-
# should include clauses from methods defined using `chain`. It is
-
# false by default for backwards compatibility.
-
1
def include_chain_clauses_in_custom_matcher_descriptions?
-
@include_chain_clauses_in_custom_matcher_descriptions ||= false
-
end
-
-
# @private
-
1
def reset_syntaxes_to_default
-
1
self.syntax = [:should, :expect]
-
1
RSpec::Expectations::Syntax.warn_about_should!
-
end
-
-
# @api private
-
# Null implementation of a backtrace formatter used by default
-
# when rspec-core is not loaded. Does no filtering.
-
1
NullBacktraceFormatter = Module.new do
-
1
def self.format_backtrace(backtrace)
-
backtrace
-
end
-
end
-
-
# Configures whether RSpec will warn about matcher use which will
-
# potentially cause false positives in tests.
-
#
-
# @param value [Boolean]
-
1
attr_writer :warn_about_potential_false_positives
-
-
# Indicates whether RSpec will warn about matcher use which will
-
# potentially cause false positives in tests, generally you want to
-
# avoid such scenarios so this defaults to `true`.
-
1
def warn_about_potential_false_positives?
-
4
@warn_about_potential_false_positives
-
end
-
end
-
-
# The configuration object.
-
# @return [RSpec::Expectations::Configuration] the configuration object
-
1
def self.configuration
-
5
@configuration ||= Configuration.new
-
end
-
-
# set default syntax
-
1
configuration.reset_syntaxes_to_default
-
end
-
end
-
1
module RSpec
-
1
module Expectations
-
# Wraps the target of an expectation.
-
#
-
# @example
-
# expect(something) # => ExpectationTarget wrapping something
-
# expect { do_something } # => ExpectationTarget wrapping the block
-
#
-
# # used with `to`
-
# expect(actual).to eq(3)
-
#
-
# # with `not_to`
-
# expect(actual).not_to eq(3)
-
#
-
# @note `ExpectationTarget` is not intended to be instantiated
-
# directly by users. Use `expect` instead.
-
1
class ExpectationTarget
-
# @private
-
# Used as a sentinel value to be able to tell when the user
-
# did not pass an argument. We can't use `nil` for that because
-
# `nil` is a valid value to pass.
-
1
UndefinedValue = Module.new
-
-
# @api private
-
1
def initialize(value)
-
9
@target = value
-
end
-
-
# @private
-
1
def self.for(value, block)
-
9
if UndefinedValue.equal?(value)
-
4
unless block
-
raise ArgumentError, "You must pass either an argument or a block to `expect`."
-
end
-
4
BlockExpectationTarget.new(block)
-
5
elsif block
-
raise ArgumentError, "You cannot pass both an argument and a block to `expect`."
-
else
-
5
new(value)
-
end
-
end
-
-
# Runs the given expectation, passing if `matcher` returns true.
-
# @example
-
# expect(value).to eq(5)
-
# expect { perform }.to raise_error
-
# @param [Matcher]
-
# matcher
-
# @param [String or Proc] message optional message to display when the expectation fails
-
# @return [Boolean] true if the expectation succeeds (else raises)
-
# @see RSpec::Matchers
-
1
def to(matcher=nil, message=nil, &block)
-
8
prevent_operator_matchers(:to) unless matcher
-
8
RSpec::Expectations::PositiveExpectationHandler.handle_matcher(@target, matcher, message, &block)
-
end
-
-
# Runs the given expectation, passing if `matcher` returns false.
-
# @example
-
# expect(value).not_to eq(5)
-
# @param [Matcher]
-
# matcher
-
# @param [String or Proc] message optional message to display when the expectation fails
-
# @return [Boolean] false if the negative expectation succeeds (else raises)
-
# @see RSpec::Matchers
-
1
def not_to(matcher=nil, message=nil, &block)
-
1
prevent_operator_matchers(:not_to) unless matcher
-
1
RSpec::Expectations::NegativeExpectationHandler.handle_matcher(@target, matcher, message, &block)
-
end
-
1
alias to_not not_to
-
-
1
private
-
-
1
def prevent_operator_matchers(verb)
-
raise ArgumentError, "The expect syntax does not support operator matchers, " \
-
"so you must pass a matcher to `##{verb}`."
-
end
-
end
-
-
# @private
-
# Validates the provided matcher to ensure it supports block
-
# expectations, in order to avoid user confusion when they
-
# use a block thinking the expectation will be on the return
-
# value of the block rather than the block itself.
-
1
class BlockExpectationTarget < ExpectationTarget
-
1
def to(matcher, message=nil, &block)
-
4
enforce_block_expectation(matcher)
-
4
super
-
end
-
-
1
def not_to(matcher, message=nil, &block)
-
enforce_block_expectation(matcher)
-
super
-
end
-
1
alias to_not not_to
-
-
1
private
-
-
1
def enforce_block_expectation(matcher)
-
4
return if supports_block_expectations?(matcher)
-
-
raise ExpectationNotMetError, "You must pass an argument rather than a block to use the provided " \
-
"matcher (#{RSpec::Support::ObjectFormatter.format(matcher)}), or the matcher must implement " \
-
"`supports_block_expectations?`."
-
end
-
-
1
def supports_block_expectations?(matcher)
-
4
matcher.supports_block_expectations?
-
rescue NoMethodError
-
false
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Expectations
-
1
class << self
-
# @private
-
1
def differ
-
RSpec::Support::Differ.new(
-
:object_preparer => lambda { |object| RSpec::Matchers::Composable.surface_descriptions_in(object) },
-
:color => RSpec::Matchers.configuration.color?
-
)
-
end
-
-
# Raises an RSpec::Expectations::ExpectationNotMetError with message.
-
# @param [String] message
-
# @param [Object] expected
-
# @param [Object] actual
-
#
-
# Adds a diff to the failure message when `expected` and `actual` are
-
# both present.
-
1
def fail_with(message, expected=nil, actual=nil)
-
unless message
-
raise ArgumentError, "Failure message is nil. Does your matcher define the " \
-
"appropriate failure_message[_when_negated] method to return a string?"
-
end
-
-
message = ::RSpec::Matchers::ExpectedsForMultipleDiffs.from(expected).message_with_diff(message, differ, actual)
-
-
RSpec::Support.notify_failure(RSpec::Expectations::ExpectationNotMetError.new message)
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Expectations
-
# @private
-
1
module ExpectationHelper
-
1
def self.check_message(msg)
-
9
unless msg.nil? || msg.respond_to?(:to_str) || msg.respond_to?(:call)
-
::Kernel.warn [
-
"WARNING: ignoring the provided expectation message argument (",
-
msg.inspect,
-
") since it is not a string or a proc."
-
].join
-
end
-
end
-
-
# Returns an RSpec-3+ compatible matcher, wrapping a legacy one
-
# in an adapter if necessary.
-
#
-
# @private
-
1
def self.modern_matcher_from(matcher)
-
LegacyMatcherAdapter::RSpec2.wrap(matcher) ||
-
9
LegacyMatcherAdapter::RSpec1.wrap(matcher) || matcher
-
end
-
-
1
def self.with_matcher(handler, matcher, message)
-
9
check_message(message)
-
9
matcher = modern_matcher_from(matcher)
-
9
yield matcher
-
ensure
-
9
::RSpec::Matchers.last_expectation_handler = handler
-
9
::RSpec::Matchers.last_matcher = matcher
-
end
-
-
1
def self.handle_failure(matcher, message, failure_message_method)
-
message = message.call if message.respond_to?(:call)
-
message ||= matcher.__send__(failure_message_method)
-
-
if matcher.respond_to?(:diffable?) && matcher.diffable?
-
::RSpec::Expectations.fail_with message, matcher.expected, matcher.actual
-
else
-
::RSpec::Expectations.fail_with message
-
end
-
end
-
end
-
-
# @private
-
1
class PositiveExpectationHandler
-
1
def self.handle_matcher(actual, initial_matcher, message=nil, &block)
-
8
ExpectationHelper.with_matcher(self, initial_matcher, message) do |matcher|
-
8
return ::RSpec::Matchers::BuiltIn::PositiveOperatorMatcher.new(actual) unless initial_matcher
-
8
matcher.matches?(actual, &block) || ExpectationHelper.handle_failure(matcher, message, :failure_message)
-
end
-
end
-
-
1
def self.verb
-
"should"
-
end
-
-
1
def self.should_method
-
:should
-
end
-
-
1
def self.opposite_should_method
-
:should_not
-
end
-
end
-
-
# @private
-
1
class NegativeExpectationHandler
-
1
def self.handle_matcher(actual, initial_matcher, message=nil, &block)
-
1
ExpectationHelper.with_matcher(self, initial_matcher, message) do |matcher|
-
1
return ::RSpec::Matchers::BuiltIn::NegativeOperatorMatcher.new(actual) unless initial_matcher
-
1
!(does_not_match?(matcher, actual, &block) || ExpectationHelper.handle_failure(matcher, message, :failure_message_when_negated))
-
end
-
end
-
-
1
def self.does_not_match?(matcher, actual, &block)
-
1
if matcher.respond_to?(:does_not_match?)
-
matcher.does_not_match?(actual, &block)
-
else
-
1
!matcher.matches?(actual, &block)
-
end
-
end
-
-
1
def self.verb
-
"should not"
-
end
-
-
1
def self.should_method
-
:should_not
-
end
-
-
1
def self.opposite_should_method
-
:should
-
end
-
end
-
-
# Wraps a matcher written against one of the legacy protocols in
-
# order to present the current protocol.
-
#
-
# @private
-
1
class LegacyMatcherAdapter < Matchers::MatcherDelegator
-
1
def initialize(matcher)
-
super
-
::RSpec.warn_deprecation(<<-EOS.gsub(/^\s+\|/, ''), :type => "legacy_matcher")
-
|#{matcher.class.name || matcher.inspect} implements a legacy RSpec matcher
-
|protocol. For the current protocol you should expose the failure messages
-
|via the `failure_message` and `failure_message_when_negated` methods.
-
|(Used from #{CallerFilter.first_non_rspec_line})
-
EOS
-
end
-
-
1
def self.wrap(matcher)
-
18
new(matcher) if interface_matches?(matcher)
-
end
-
-
# Starting in RSpec 1.2 (and continuing through all 2.x releases),
-
# the failure message protocol was:
-
# * `failure_message_for_should`
-
# * `failure_message_for_should_not`
-
# @private
-
1
class RSpec2 < self
-
1
def failure_message
-
base_matcher.failure_message_for_should
-
end
-
-
1
def failure_message_when_negated
-
base_matcher.failure_message_for_should_not
-
end
-
-
1
def self.interface_matches?(matcher)
-
(
-
!matcher.respond_to?(:failure_message) &&
-
9
matcher.respond_to?(:failure_message_for_should)
-
) || (
-
!matcher.respond_to?(:failure_message_when_negated) &&
-
9
matcher.respond_to?(:failure_message_for_should_not)
-
9
)
-
end
-
end
-
-
# Before RSpec 1.2, the failure message protocol was:
-
# * `failure_message`
-
# * `negative_failure_message`
-
# @private
-
1
class RSpec1 < self
-
1
def failure_message
-
base_matcher.failure_message
-
end
-
-
1
def failure_message_when_negated
-
base_matcher.negative_failure_message
-
end
-
-
# Note: `failure_message` is part of the RSpec 3 protocol
-
# (paired with `failure_message_when_negated`), so we don't check
-
# for `failure_message` here.
-
1
def self.interface_matches?(matcher)
-
!matcher.respond_to?(:failure_message_when_negated) &&
-
9
matcher.respond_to?(:negative_failure_message)
-
end
-
end
-
end
-
-
# RSpec 3.0 was released with the class name misspelled. For SemVer compatibility,
-
# we will provide this misspelled alias until 4.0.
-
# @deprecated Use LegacyMatcherAdapter instead.
-
# @private
-
1
LegacyMacherAdapter = LegacyMatcherAdapter
-
end
-
end
-
1
module RSpec
-
1
module Expectations
-
# @api private
-
# Provides methods for enabling and disabling the available
-
# syntaxes provided by rspec-expectations.
-
1
module Syntax
-
1
module_function
-
-
# @api private
-
# Determines where we add `should` and `should_not`.
-
1
def default_should_host
-
2
@default_should_host ||= ::Object.ancestors.last
-
end
-
-
# @api private
-
# Instructs rspec-expectations to warn on first usage of `should` or `should_not`.
-
# Enabled by default. This is largely here to facilitate testing.
-
1
def warn_about_should!
-
1
@warn_about_should = true
-
end
-
-
# @api private
-
# Generates a deprecation warning for the given method if no warning
-
# has already been issued.
-
1
def warn_about_should_unless_configured(method_name)
-
return unless @warn_about_should
-
-
RSpec.deprecate(
-
"Using `#{method_name}` from rspec-expectations' old `:should` syntax without explicitly enabling the syntax",
-
:replacement => "the new `:expect` syntax or explicitly enable `:should` with `config.expect_with(:rspec) { |c| c.syntax = :should }`"
-
)
-
-
@warn_about_should = false
-
end
-
-
# @api private
-
# Enables the `should` syntax.
-
1
def enable_should(syntax_host=default_should_host)
-
1
@warn_about_should = false if syntax_host == default_should_host
-
1
return if should_enabled?(syntax_host)
-
-
1
syntax_host.module_exec do
-
1
def should(matcher=nil, message=nil, &block)
-
::RSpec::Expectations::Syntax.warn_about_should_unless_configured(__method__)
-
::RSpec::Expectations::PositiveExpectationHandler.handle_matcher(self, matcher, message, &block)
-
end
-
-
1
def should_not(matcher=nil, message=nil, &block)
-
::RSpec::Expectations::Syntax.warn_about_should_unless_configured(__method__)
-
::RSpec::Expectations::NegativeExpectationHandler.handle_matcher(self, matcher, message, &block)
-
end
-
end
-
end
-
-
# @api private
-
# Disables the `should` syntax.
-
1
def disable_should(syntax_host=default_should_host)
-
return unless should_enabled?(syntax_host)
-
-
syntax_host.module_exec do
-
undef should
-
undef should_not
-
end
-
end
-
-
# @api private
-
# Enables the `expect` syntax.
-
1
def enable_expect(syntax_host=::RSpec::Matchers)
-
1
return if expect_enabled?(syntax_host)
-
-
1
syntax_host.module_exec do
-
1
def expect(value=::RSpec::Expectations::ExpectationTarget::UndefinedValue, &block)
-
9
::RSpec::Expectations::ExpectationTarget.for(value, block)
-
end
-
end
-
end
-
-
# @api private
-
# Disables the `expect` syntax.
-
1
def disable_expect(syntax_host=::RSpec::Matchers)
-
return unless expect_enabled?(syntax_host)
-
-
syntax_host.module_exec do
-
undef expect
-
end
-
end
-
-
# @api private
-
# Indicates whether or not the `should` syntax is enabled.
-
1
def should_enabled?(syntax_host=default_should_host)
-
1
syntax_host.method_defined?(:should)
-
end
-
-
# @api private
-
# Indicates whether or not the `expect` syntax is enabled.
-
1
def expect_enabled?(syntax_host=::RSpec::Matchers)
-
1
syntax_host.method_defined?(:expect)
-
end
-
end
-
end
-
end
-
-
1
if defined?(BasicObject)
-
# The legacy `:should` syntax adds the following methods directly to
-
# `BasicObject` so that they are available off of any object. Note, however,
-
# that this syntax does not always play nice with delegate/proxy objects.
-
# We recommend you use the non-monkeypatching `:expect` syntax instead.
-
1
class BasicObject
-
# @method should
-
# Passes if `matcher` returns true. Available on every `Object`.
-
# @example
-
# actual.should eq expected
-
# actual.should match /expression/
-
# @param [Matcher]
-
# matcher
-
# @param [String] message optional message to display when the expectation fails
-
# @return [Boolean] true if the expectation succeeds (else raises)
-
# @note This is only available when you have enabled the `:should` syntax.
-
# @see RSpec::Matchers
-
-
# @method should_not
-
# Passes if `matcher` returns false. Available on every `Object`.
-
# @example
-
# actual.should_not eq expected
-
# @param [Matcher]
-
# matcher
-
# @param [String] message optional message to display when the expectation fails
-
# @return [Boolean] false if the negative expectation succeeds (else raises)
-
# @note This is only available when you have enabled the `:should` syntax.
-
# @see RSpec::Matchers
-
end
-
end
-
1
module RSpec
-
1
module Expectations
-
# @private
-
1
module Version
-
1
STRING = '3.4.0'
-
end
-
end
-
end
-
1
require 'rspec/support'
-
1
RSpec::Support.require_rspec_support 'matcher_definition'
-
10
RSpec::Support.define_optimized_require_for_rspec(:matchers) { |f| require_relative(f) }
-
-
%w[
-
english_phrasing
-
composable
-
built_in
-
generated_descriptions
-
dsl
-
matcher_delegator
-
aliased_matcher
-
expecteds_for_multiple_diffs
-
9
].each { |file| RSpec::Support.require_rspec_matchers(file) }
-
-
# RSpec's top level namespace. All of rspec-expectations is contained
-
# in the `RSpec::Expectations` and `RSpec::Matchers` namespaces.
-
1
module RSpec
-
# RSpec::Matchers provides a number of useful matchers we use to define
-
# expectations. Any object that implements the [matcher protocol](Matchers/MatcherProtocol)
-
# can be used as a matcher.
-
#
-
# ## Predicates
-
#
-
# In addition to matchers that are defined explicitly, RSpec will create
-
# custom matchers on the fly for any arbitrary predicate, giving your specs a
-
# much more natural language feel.
-
#
-
# A Ruby predicate is a method that ends with a "?" and returns true or false.
-
# Common examples are `empty?`, `nil?`, and `instance_of?`.
-
#
-
# All you need to do is write `expect(..).to be_` followed by the predicate
-
# without the question mark, and RSpec will figure it out from there.
-
# For example:
-
#
-
# expect([]).to be_empty # => [].empty?() | passes
-
# expect([]).not_to be_empty # => [].empty?() | fails
-
#
-
# In addtion to prefixing the predicate matchers with "be_", you can also use "be_a_"
-
# and "be_an_", making your specs read much more naturally:
-
#
-
# expect("a string").to be_an_instance_of(String) # =>"a string".instance_of?(String) # passes
-
#
-
# expect(3).to be_a_kind_of(Fixnum) # => 3.kind_of?(Numeric) | passes
-
# expect(3).to be_a_kind_of(Numeric) # => 3.kind_of?(Numeric) | passes
-
# expect(3).to be_an_instance_of(Fixnum) # => 3.instance_of?(Fixnum) | passes
-
# expect(3).not_to be_an_instance_of(Numeric) # => 3.instance_of?(Numeric) | fails
-
#
-
# RSpec will also create custom matchers for predicates like `has_key?`. To
-
# use this feature, just state that the object should have_key(:key) and RSpec will
-
# call has_key?(:key) on the target. For example:
-
#
-
# expect(:a => "A").to have_key(:a)
-
# expect(:a => "A").to have_key(:b) # fails
-
#
-
# You can use this feature to invoke any predicate that begins with "has_", whether it is
-
# part of the Ruby libraries (like `Hash#has_key?`) or a method you wrote on your own class.
-
#
-
# Note that RSpec does not provide composable aliases for these dynamic predicate
-
# matchers. You can easily define your own aliases, though:
-
#
-
# RSpec::Matchers.alias_matcher :a_user_who_is_an_admin, :be_an_admin
-
# expect(user_list).to include(a_user_who_is_an_admin)
-
#
-
# ## Custom Matchers
-
#
-
# When you find that none of the stock matchers provide a natural feeling
-
# expectation, you can very easily write your own using RSpec's matcher DSL
-
# or writing one from scratch.
-
#
-
# ### Matcher DSL
-
#
-
# Imagine that you are writing a game in which players can be in various
-
# zones on a virtual board. To specify that bob should be in zone 4, you
-
# could say:
-
#
-
# expect(bob.current_zone).to eql(Zone.new("4"))
-
#
-
# But you might find it more expressive to say:
-
#
-
# expect(bob).to be_in_zone("4")
-
#
-
# and/or
-
#
-
# expect(bob).not_to be_in_zone("3")
-
#
-
# You can create such a matcher like so:
-
#
-
# RSpec::Matchers.define :be_in_zone do |zone|
-
# match do |player|
-
# player.in_zone?(zone)
-
# end
-
# end
-
#
-
# This will generate a <tt>be_in_zone</tt> method that returns a matcher
-
# with logical default messages for failures. You can override the failure
-
# messages and the generated description as follows:
-
#
-
# RSpec::Matchers.define :be_in_zone do |zone|
-
# match do |player|
-
# player.in_zone?(zone)
-
# end
-
#
-
# failure_message do |player|
-
# # generate and return the appropriate string.
-
# end
-
#
-
# failure_message_when_negated do |player|
-
# # generate and return the appropriate string.
-
# end
-
#
-
# description do
-
# # generate and return the appropriate string.
-
# end
-
# end
-
#
-
# Each of the message-generation methods has access to the block arguments
-
# passed to the <tt>create</tt> method (in this case, <tt>zone</tt>). The
-
# failure message methods (<tt>failure_message</tt> and
-
# <tt>failure_message_when_negated</tt>) are passed the actual value (the
-
# receiver of <tt>expect(..)</tt> or <tt>expect(..).not_to</tt>).
-
#
-
# ### Custom Matcher from scratch
-
#
-
# You could also write a custom matcher from scratch, as follows:
-
#
-
# class BeInZone
-
# def initialize(expected)
-
# @expected = expected
-
# end
-
#
-
# def matches?(target)
-
# @target = target
-
# @target.current_zone.eql?(Zone.new(@expected))
-
# end
-
#
-
# def failure_message
-
# "expected #{@target.inspect} to be in Zone #{@expected}"
-
# end
-
#
-
# def failure_message_when_negated
-
# "expected #{@target.inspect} not to be in Zone #{@expected}"
-
# end
-
# end
-
#
-
# ... and a method like this:
-
#
-
# def be_in_zone(expected)
-
# BeInZone.new(expected)
-
# end
-
#
-
# And then expose the method to your specs. This is normally done
-
# by including the method and the class in a module, which is then
-
# included in your spec:
-
#
-
# module CustomGameMatchers
-
# class BeInZone
-
# # ...
-
# end
-
#
-
# def be_in_zone(expected)
-
# # ...
-
# end
-
# end
-
#
-
# describe "Player behaviour" do
-
# include CustomGameMatchers
-
# # ...
-
# end
-
#
-
# or you can include in globally in a spec_helper.rb file <tt>require</tt>d
-
# from your spec file(s):
-
#
-
# RSpec::configure do |config|
-
# config.include(CustomGameMatchers)
-
# end
-
#
-
# ### Making custom matchers composable
-
#
-
# RSpec's built-in matchers are designed to be composed, in expressions like:
-
#
-
# expect(["barn", 2.45]).to contain_exactly(
-
# a_value_within(0.1).of(2.5),
-
# a_string_starting_with("bar")
-
# )
-
#
-
# Custom matchers can easily participate in composed matcher expressions like these.
-
# Include {RSpec::Matchers::Composable} in your custom matcher to make it support
-
# being composed (matchers defined using the DSL have this included automatically).
-
# Within your matcher's `matches?` method (or the `match` block, if using the DSL),
-
# use `values_match?(expected, actual)` rather than `expected == actual`.
-
# Under the covers, `values_match?` is able to match arbitrary
-
# nested data structures containing a mix of both matchers and non-matcher objects.
-
# It uses `===` and `==` to perform the matching, considering the values to
-
# match if either returns `true`. The `Composable` mixin also provides some helper
-
# methods for surfacing the matcher descriptions within your matcher's description
-
# or failure messages.
-
#
-
# RSpec's built-in matchers each have a number of aliases that rephrase the matcher
-
# from a verb phrase (such as `be_within`) to a noun phrase (such as `a_value_within`),
-
# which reads better when the matcher is passed as an argument in a composed matcher
-
# expressions, and also uses the noun-phrase wording in the matcher's `description`,
-
# for readable failure messages. You can alias your custom matchers in similar fashion
-
# using {RSpec::Matchers.alias_matcher}.
-
1
module Matchers
-
# @method expect
-
# Supports `expect(actual).to matcher` syntax by wrapping `actual` in an
-
# `ExpectationTarget`.
-
# @example
-
# expect(actual).to eq(expected)
-
# expect(actual).not_to eq(expected)
-
# @return [ExpectationTarget]
-
# @see ExpectationTarget#to
-
# @see ExpectationTarget#not_to
-
-
# Defines a matcher alias. The returned matcher's `description` will be overriden
-
# to reflect the phrasing of the new name, which will be used in failure messages
-
# when passed as an argument to another matcher in a composed matcher expression.
-
#
-
# @param new_name [Symbol] the new name for the matcher
-
# @param old_name [Symbol] the original name for the matcher
-
# @param options [Hash] options for the aliased matcher
-
# @option options [Class] :klass the ruby class to use as the decorator. (Not normally used).
-
# @yield [String] optional block that, when given, is used to define the overriden
-
# logic. The yielded arg is the original description or failure message. If no
-
# block is provided, a default override is used based on the old and new names.
-
#
-
# @example
-
# RSpec::Matchers.alias_matcher :a_list_that_sums_to, :sum_to
-
# sum_to(3).description # => "sum to 3"
-
# a_list_that_sums_to(3).description # => "a list that sums to 3"
-
#
-
# @example
-
# RSpec::Matchers.alias_matcher :a_list_sorted_by, :be_sorted_by do |description|
-
# description.sub("be sorted by", "a list sorted by")
-
# end
-
#
-
# be_sorted_by(:age).description # => "be sorted by age"
-
# a_list_sorted_by(:age).description # => "a list sorted by age"
-
#
-
# @!macro [attach] alias_matcher
-
# @!parse
-
# alias $1 $2
-
1
def self.alias_matcher(new_name, old_name, options={}, &description_override)
-
description_override ||= lambda do |old_desc|
-
old_desc.gsub(EnglishPhrasing.split_words(old_name), EnglishPhrasing.split_words(new_name))
-
57
end
-
113
klass = options.fetch(:klass) { AliasedMatcher }
-
-
57
define_method(new_name) do |*args, &block|
-
matcher = __send__(old_name, *args, &block)
-
klass.new(matcher, description_override)
-
end
-
end
-
-
# Defines a negated matcher. The returned matcher's `description` and `failure_message`
-
# will be overriden to reflect the phrasing of the new name, and the match logic will
-
# be based on the original matcher but negated.
-
#
-
# @param negated_name [Symbol] the name for the negated matcher
-
# @param base_name [Symbol] the name of the original matcher that will be negated
-
# @yield [String] optional block that, when given, is used to define the overriden
-
# logic. The yielded arg is the original description or failure message. If no
-
# block is provided, a default override is used based on the old and new names.
-
#
-
# @example
-
# RSpec::Matchers.define_negated_matcher :exclude, :include
-
# include(1, 2).description # => "include 1 and 2"
-
# exclude(1, 2).description # => "exclude 1 and 2"
-
#
-
# @note While the most obvious negated form may be to add a `not_` prefix,
-
# the failure messages you get with that form can be confusing (e.g.
-
# "expected [actual] to not [verb], but did not"). We've found it works
-
# best to find a more positive name for the negated form, such as
-
# `avoid_changing` rather than `not_change`.
-
1
def self.define_negated_matcher(negated_name, base_name, &description_override)
-
alias_matcher(negated_name, base_name, :klass => AliasedNegatedMatcher, &description_override)
-
end
-
-
# Allows multiple expectations in the provided block to fail, and then
-
# aggregates them into a single exception, rather than aborting on the
-
# first expectation failure like normal. This allows you to see all
-
# failures from an entire set of expectations without splitting each
-
# off into its own example (which may slow things down if the example
-
# setup is expensive).
-
#
-
# @param label [String] label for this aggregation block, which will be
-
# included in the aggregated exception message.
-
# @param metadata [Hash] additional metadata about this failure aggregation
-
# block. If multiple expectations fail, it will be exposed from the
-
# {Expectations::MultipleExpectationsNotMetError} exception. Mostly
-
# intended for internal RSpec use but you can use it as well.
-
# @yield Block containing as many expectation as you want. The block is
-
# simply yielded to, so you can trust that anything that works outside
-
# the block should work within it.
-
# @raise [Expectations::MultipleExpectationsNotMetError] raised when
-
# multiple expectations fail.
-
# @raise [Expectations::ExpectationNotMetError] raised when a single
-
# expectation fails.
-
# @raise [Exception] other sorts of exceptions will be raised as normal.
-
#
-
# @example
-
# aggregate_failures("verifying response") do
-
# expect(response.status).to eq(200)
-
# expect(response.headers).to include("Content-Type" => "text/plain")
-
# expect(response.body).to include("Success")
-
# end
-
#
-
# @note The implementation of this feature uses a thread-local variable,
-
# which means that if you have an expectation failure in another thread,
-
# it'll abort like normal.
-
1
def aggregate_failures(label=nil, metadata={}, &block)
-
Expectations::FailureAggregator.new(label, metadata).aggregate(&block)
-
end
-
-
# Passes if actual is truthy (anything but false or nil)
-
1
def be_truthy
-
BuiltIn::BeTruthy.new
-
end
-
1
alias_matcher :a_truthy_value, :be_truthy
-
-
# Passes if actual is falsey (false or nil)
-
1
def be_falsey
-
BuiltIn::BeFalsey.new
-
end
-
1
alias_matcher :be_falsy, :be_falsey
-
1
alias_matcher :a_falsey_value, :be_falsey
-
1
alias_matcher :a_falsy_value, :be_falsey
-
-
# Passes if actual is nil
-
1
def be_nil
-
1
BuiltIn::BeNil.new
-
end
-
1
alias_matcher :a_nil_value, :be_nil
-
-
# @example
-
# expect(actual).to be_truthy
-
# expect(actual).to be_falsey
-
# expect(actual).to be_nil
-
# expect(actual).to be_[arbitrary_predicate](*args)
-
# expect(actual).not_to be_nil
-
# expect(actual).not_to be_[arbitrary_predicate](*args)
-
#
-
# Given true, false, or nil, will pass if actual value is true, false or
-
# nil (respectively). Given no args means the caller should satisfy an if
-
# condition (to be or not to be).
-
#
-
# Predicates are any Ruby method that ends in a "?" and returns true or
-
# false. Given be_ followed by arbitrary_predicate (without the "?"),
-
# RSpec will match convert that into a query against the target object.
-
#
-
# The arbitrary_predicate feature will handle any predicate prefixed with
-
# "be_an_" (e.g. be_an_instance_of), "be_a_" (e.g. be_a_kind_of) or "be_"
-
# (e.g. be_empty), letting you choose the prefix that best suits the
-
# predicate.
-
1
def be(*args)
-
1
args.empty? ? Matchers::BuiltIn::Be.new : equal(*args)
-
end
-
1
alias_matcher :a_value, :be, :klass => AliasedMatcherWithOperatorSupport
-
-
# passes if target.kind_of?(klass)
-
1
def be_a(klass)
-
be_a_kind_of(klass)
-
end
-
1
alias_method :be_an, :be_a
-
-
# Passes if actual.instance_of?(expected)
-
#
-
# @example
-
# expect(5).to be_an_instance_of(Fixnum)
-
# expect(5).not_to be_an_instance_of(Numeric)
-
# expect(5).not_to be_an_instance_of(Float)
-
1
def be_an_instance_of(expected)
-
BuiltIn::BeAnInstanceOf.new(expected)
-
end
-
1
alias_method :be_instance_of, :be_an_instance_of
-
1
alias_matcher :an_instance_of, :be_an_instance_of
-
-
# Passes if actual.kind_of?(expected)
-
#
-
# @example
-
# expect(5).to be_a_kind_of(Fixnum)
-
# expect(5).to be_a_kind_of(Numeric)
-
# expect(5).not_to be_a_kind_of(Float)
-
1
def be_a_kind_of(expected)
-
BuiltIn::BeAKindOf.new(expected)
-
end
-
1
alias_method :be_kind_of, :be_a_kind_of
-
1
alias_matcher :a_kind_of, :be_a_kind_of
-
-
# Passes if actual.between?(min, max). Works with any Comparable object,
-
# including String, Symbol, Time, or Numeric (Fixnum, Bignum, Integer,
-
# Float, Complex, and Rational).
-
#
-
# By default, `be_between` is inclusive (i.e. passes when given either the max or min value),
-
# but you can make it `exclusive` by chaining that off the matcher.
-
#
-
# @example
-
# expect(5).to be_between(1, 10)
-
# expect(11).not_to be_between(1, 10)
-
# expect(10).not_to be_between(1, 10).exclusive
-
1
def be_between(min, max)
-
BuiltIn::BeBetween.new(min, max)
-
end
-
1
alias_matcher :a_value_between, :be_between
-
-
# Passes if actual == expected +/- delta
-
#
-
# @example
-
# expect(result).to be_within(0.5).of(3.0)
-
# expect(result).not_to be_within(0.5).of(3.0)
-
1
def be_within(delta)
-
BuiltIn::BeWithin.new(delta)
-
end
-
1
alias_matcher :a_value_within, :be_within
-
1
alias_matcher :within, :be_within
-
-
# Applied to a proc, specifies that its execution will cause some value to
-
# change.
-
#
-
# @param [Object] receiver
-
# @param [Symbol] message the message to send the receiver
-
#
-
# You can either pass <tt>receiver</tt> and <tt>message</tt>, or a block,
-
# but not both.
-
#
-
# When passing a block, it must use the `{ ... }` format, not
-
# do/end, as `{ ... }` binds to the `change` method, whereas do/end
-
# would errantly bind to the `expect(..).to` or `expect(...).not_to` method.
-
#
-
# You can chain any of the following off of the end to specify details
-
# about the change:
-
#
-
# * `from`
-
# * `to`
-
#
-
# or any one of:
-
#
-
# * `by`
-
# * `by_at_least`
-
# * `by_at_most`
-
#
-
# @example
-
# expect {
-
# team.add_player(player)
-
# }.to change(roster, :count)
-
#
-
# expect {
-
# team.add_player(player)
-
# }.to change(roster, :count).by(1)
-
#
-
# expect {
-
# team.add_player(player)
-
# }.to change(roster, :count).by_at_least(1)
-
#
-
# expect {
-
# team.add_player(player)
-
# }.to change(roster, :count).by_at_most(1)
-
#
-
# string = "string"
-
# expect {
-
# string.reverse!
-
# }.to change { string }.from("string").to("gnirts")
-
#
-
# string = "string"
-
# expect {
-
# string
-
# }.not_to change { string }.from("string")
-
#
-
# expect {
-
# person.happy_birthday
-
# }.to change(person, :birthday).from(32).to(33)
-
#
-
# expect {
-
# employee.develop_great_new_social_networking_app
-
# }.to change(employee, :title).from("Mail Clerk").to("CEO")
-
#
-
# expect {
-
# doctor.leave_office
-
# }.to change(doctor, :sign).from(/is in/).to(/is out/)
-
#
-
# user = User.new(:type => "admin")
-
# expect {
-
# user.symbolize_type
-
# }.to change(user, :type).from(String).to(Symbol)
-
#
-
# == Notes
-
#
-
# Evaluates `receiver.message` or `block` before and after it
-
# evaluates the block passed to `expect`.
-
#
-
# `expect( ... ).not_to change` supports the form that specifies `from`
-
# (which specifies what you expect the starting, unchanged value to be)
-
# but does not support forms with subsequent calls to `by`, `by_at_least`,
-
# `by_at_most` or `to`.
-
1
def change(receiver=nil, message=nil, &block)
-
BuiltIn::Change.new(receiver, message, &block)
-
end
-
1
alias_matcher :a_block_changing, :change
-
1
alias_matcher :changing, :change
-
-
# Passes if actual contains all of the expected regardless of order.
-
# This works for collections. Pass in multiple args and it will only
-
# pass if all args are found in collection.
-
#
-
# @note This is also available using the `=~` operator with `should`,
-
# but `=~` is not supported with `expect`.
-
#
-
# @example
-
# expect([1, 2, 3]).to contain_exactly(1, 2, 3)
-
# expect([1, 2, 3]).to contain_exactly(1, 3, 2)
-
#
-
# @see #match_array
-
1
def contain_exactly(*items)
-
BuiltIn::ContainExactly.new(items)
-
end
-
1
alias_matcher :a_collection_containing_exactly, :contain_exactly
-
1
alias_matcher :containing_exactly, :contain_exactly
-
-
# Passes if actual covers expected. This works for
-
# Ranges. You can also pass in multiple args
-
# and it will only pass if all args are found in Range.
-
#
-
# @example
-
# expect(1..10).to cover(5)
-
# expect(1..10).to cover(4, 6)
-
# expect(1..10).to cover(4, 6, 11) # fails
-
# expect(1..10).not_to cover(11)
-
# expect(1..10).not_to cover(5) # fails
-
#
-
# ### Warning:: Ruby >= 1.9 only
-
1
def cover(*values)
-
BuiltIn::Cover.new(*values)
-
end
-
1
alias_matcher :a_range_covering, :cover
-
1
alias_matcher :covering, :cover
-
-
# Matches if the actual value ends with the expected value(s). In the case
-
# of a string, matches against the last `expected.length` characters of the
-
# actual string. In the case of an array, matches against the last
-
# `expected.length` elements of the actual array.
-
#
-
# @example
-
# expect("this string").to end_with "string"
-
# expect([0, 1, 2, 3, 4]).to end_with 4
-
# expect([0, 2, 3, 4, 4]).to end_with 3, 4
-
1
def end_with(*expected)
-
BuiltIn::EndWith.new(*expected)
-
end
-
1
alias_matcher :a_collection_ending_with, :end_with
-
1
alias_matcher :a_string_ending_with, :end_with
-
1
alias_matcher :ending_with, :end_with
-
-
# Passes if <tt>actual == expected</tt>.
-
#
-
# See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more
-
# information about equality in Ruby.
-
#
-
# @example
-
# expect(5).to eq(5)
-
# expect(5).not_to eq(3)
-
1
def eq(expected)
-
3
BuiltIn::Eq.new(expected)
-
end
-
1
alias_matcher :an_object_eq_to, :eq
-
1
alias_matcher :eq_to, :eq
-
-
# Passes if `actual.eql?(expected)`
-
#
-
# See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more
-
# information about equality in Ruby.
-
#
-
# @example
-
# expect(5).to eql(5)
-
# expect(5).not_to eql(3)
-
1
def eql(expected)
-
BuiltIn::Eql.new(expected)
-
end
-
1
alias_matcher :an_object_eql_to, :eql
-
1
alias_matcher :eql_to, :eql
-
-
# Passes if <tt>actual.equal?(expected)</tt> (object identity).
-
#
-
# See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more
-
# information about equality in Ruby.
-
#
-
# @example
-
# expect(5).to equal(5) # Fixnums are equal
-
# expect("5").not_to equal("5") # Strings that look the same are not the same object
-
1
def equal(expected)
-
1
BuiltIn::Equal.new(expected)
-
end
-
1
alias_matcher :an_object_equal_to, :equal
-
1
alias_matcher :equal_to, :equal
-
-
# Passes if `actual.exist?` or `actual.exists?`
-
#
-
# @example
-
# expect(File).to exist("path/to/file")
-
1
def exist(*args)
-
BuiltIn::Exist.new(*args)
-
end
-
1
alias_matcher :an_object_existing, :exist
-
1
alias_matcher :existing, :exist
-
-
# Passes if actual's attribute values match the expected attributes hash.
-
# This works no matter how you define your attribute readers.
-
#
-
# @example
-
# Person = Struct.new(:name, :age)
-
# person = Person.new("Bob", 32)
-
#
-
# expect(person).to have_attributes(:name => "Bob", :age => 32)
-
# expect(person).to have_attributes(:name => a_string_starting_with("B"), :age => (a_value > 30) )
-
#
-
# @note It will fail if actual doesn't respond to any of the expected attributes.
-
#
-
# @example
-
# expect(person).to have_attributes(:color => "red")
-
1
def have_attributes(expected)
-
BuiltIn::HaveAttributes.new(expected)
-
end
-
1
alias_matcher :an_object_having_attributes, :have_attributes
-
-
# Passes if actual includes expected. This works for
-
# collections and Strings. You can also pass in multiple args
-
# and it will only pass if all args are found in collection.
-
#
-
# @example
-
# expect([1,2,3]).to include(3)
-
# expect([1,2,3]).to include(2,3)
-
# expect([1,2,3]).to include(2,3,4) # fails
-
# expect([1,2,3]).not_to include(4)
-
# expect("spread").to include("read")
-
# expect("spread").not_to include("red")
-
# expect(:a => 1, :b => 2).to include(:a)
-
# expect(:a => 1, :b => 2).to include(:a, :b)
-
# expect(:a => 1, :b => 2).to include(:a => 1)
-
# expect(:a => 1, :b => 2).to include(:b => 2, :a => 1)
-
# expect(:a => 1, :b => 2).to include(:c) # fails
-
# expect(:a => 1, :b => 2).not_to include(:a => 2)
-
1
def include(*expected)
-
BuiltIn::Include.new(*expected)
-
end
-
1
alias_matcher :a_collection_including, :include
-
1
alias_matcher :a_string_including, :include
-
1
alias_matcher :a_hash_including, :include
-
1
alias_matcher :including, :include
-
-
# Passes if the provided matcher passes when checked against all
-
# elements of the collection.
-
#
-
# @example
-
# expect([1, 3, 5]).to all be_odd
-
# expect([1, 3, 6]).to all be_odd # fails
-
#
-
# @note The negative form `not_to all` is not supported. Instead
-
# use `not_to include` or pass a negative form of a matcher
-
# as the argument (e.g. `all exclude(:foo)`).
-
#
-
# @note You can also use this with compound matchers as well.
-
#
-
# @example
-
# expect([1, 3, 5]).to all( be_odd.and be_an(Integer) )
-
1
def all(expected)
-
BuiltIn::All.new(expected)
-
end
-
-
# Given a `Regexp` or `String`, passes if `actual.match(pattern)`
-
# Given an arbitrary nested data structure (e.g. arrays and hashes),
-
# matches if `expected === actual` || `actual == expected` for each
-
# pair of elements.
-
#
-
# @example
-
# expect(email).to match(/^([^\s]+)((?:[-a-z0-9]+\.)+[a-z]{2,})$/i)
-
# expect(email).to match("@example.com")
-
#
-
# @example
-
# hash = {
-
# :a => {
-
# :b => ["foo", 5],
-
# :c => { :d => 2.05 }
-
# }
-
# }
-
#
-
# expect(hash).to match(
-
# :a => {
-
# :b => a_collection_containing_exactly(
-
# a_string_starting_with("f"),
-
# an_instance_of(Fixnum)
-
# ),
-
# :c => { :d => (a_value < 3) }
-
# }
-
# )
-
#
-
# @note The `match_regex` alias is deprecated and is not recommended for use.
-
# It was added in 2.12.1 to facilitate its use from within custom
-
# matchers (due to how the custom matcher DSL was evaluated in 2.x,
-
# `match` could not be used there), but is no longer needed in 3.x.
-
1
def match(expected)
-
BuiltIn::Match.new(expected)
-
end
-
1
alias_matcher :match_regex, :match
-
1
alias_matcher :an_object_matching, :match
-
1
alias_matcher :a_string_matching, :match
-
1
alias_matcher :matching, :match
-
-
# An alternate form of `contain_exactly` that accepts
-
# the expected contents as a single array arg rather
-
# that splatted out as individual items.
-
#
-
# @example
-
# expect(results).to contain_exactly(1, 2)
-
# # is identical to:
-
# expect(results).to match_array([1, 2])
-
#
-
# @see #contain_exactly
-
1
def match_array(items)
-
contain_exactly(*items)
-
end
-
-
# With no arg, passes if the block outputs `to_stdout` or `to_stderr`.
-
# With a string, passes if the block outputs that specific string `to_stdout` or `to_stderr`.
-
# With a regexp or matcher, passes if the block outputs a string `to_stdout` or `to_stderr` that matches.
-
#
-
# To capture output from any spawned subprocess as well, use `to_stdout_from_any_process` or
-
# `to_stderr_from_any_process`. Output from any process that inherits the main process's corresponding
-
# standard stream will be captured.
-
#
-
# @example
-
# expect { print 'foo' }.to output.to_stdout
-
# expect { print 'foo' }.to output('foo').to_stdout
-
# expect { print 'foo' }.to output(/foo/).to_stdout
-
#
-
# expect { do_something }.to_not output.to_stdout
-
#
-
# expect { warn('foo') }.to output.to_stderr
-
# expect { warn('foo') }.to output('foo').to_stderr
-
# expect { warn('foo') }.to output(/foo/).to_stderr
-
#
-
# expect { do_something }.to_not output.to_stderr
-
#
-
# expect { system('echo foo') }.to output("foo\n").to_stdout_from_any_process
-
# expect { system('echo foo', out: :err) }.to output("foo\n").to_stderr_from_any_process
-
#
-
# @note `to_stdout` and `to_stderr` work by temporarily replacing `$stdout` or `$stderr`,
-
# so they're not able to intercept stream output that explicitly uses `STDOUT`/`STDERR`
-
# or that uses a reference to `$stdout`/`$stderr` that was stored before the
-
# matcher was used.
-
# @note `to_stdout_from_any_process` and `to_stderr_from_any_process` use Tempfiles, and
-
# are thus significantly (~30x) slower than `to_stdout` and `to_stderr`.
-
1
def output(expected=nil)
-
BuiltIn::Output.new(expected)
-
end
-
1
alias_matcher :a_block_outputting, :output
-
-
# With no args, matches if any error is raised.
-
# With a named error, matches only if that specific error is raised.
-
# With a named error and messsage specified as a String, matches only if both match.
-
# With a named error and messsage specified as a Regexp, matches only if both match.
-
# Pass an optional block to perform extra verifications on the exception matched
-
#
-
# @example
-
# expect { do_something_risky }.to raise_error
-
# expect { do_something_risky }.to raise_error(PoorRiskDecisionError)
-
# expect { do_something_risky }.to raise_error(PoorRiskDecisionError) { |error| expect(error.data).to eq 42 }
-
# expect { do_something_risky }.to raise_error(PoorRiskDecisionError, "that was too risky")
-
# expect { do_something_risky }.to raise_error(PoorRiskDecisionError, /oo ri/)
-
#
-
# expect { do_something_risky }.not_to raise_error
-
1
def raise_error(error=nil, message=nil, &block)
-
4
BuiltIn::RaiseError.new(error, message, &block)
-
end
-
1
alias_method :raise_exception, :raise_error
-
-
1
alias_matcher :a_block_raising, :raise_error do |desc|
-
desc.sub("raise", "a block raising")
-
end
-
-
1
alias_matcher :raising, :raise_error do |desc|
-
desc.sub("raise", "raising")
-
end
-
-
# Matches if the target object responds to all of the names
-
# provided. Names can be Strings or Symbols.
-
#
-
# @example
-
# expect("string").to respond_to(:length)
-
#
-
1
def respond_to(*names)
-
BuiltIn::RespondTo.new(*names)
-
end
-
1
alias_matcher :an_object_responding_to, :respond_to
-
1
alias_matcher :responding_to, :respond_to
-
-
# Passes if the submitted block returns true. Yields target to the
-
# block.
-
#
-
# Generally speaking, this should be thought of as a last resort when
-
# you can't find any other way to specify the behaviour you wish to
-
# specify.
-
#
-
# If you do find yourself in such a situation, you could always write
-
# a custom matcher, which would likely make your specs more expressive.
-
#
-
# @param description [String] optional description to be used for this matcher.
-
#
-
# @example
-
# expect(5).to satisfy { |n| n > 3 }
-
# expect(5).to satisfy("be greater than 3") { |n| n > 3 }
-
1
def satisfy(description="satisfy block", &block)
-
BuiltIn::Satisfy.new(description, &block)
-
end
-
1
alias_matcher :an_object_satisfying, :satisfy
-
1
alias_matcher :satisfying, :satisfy
-
-
# Matches if the actual value starts with the expected value(s). In the
-
# case of a string, matches against the first `expected.length` characters
-
# of the actual string. In the case of an array, matches against the first
-
# `expected.length` elements of the actual array.
-
#
-
# @example
-
# expect("this string").to start_with "this s"
-
# expect([0, 1, 2, 3, 4]).to start_with 0
-
# expect([0, 2, 3, 4, 4]).to start_with 0, 1
-
1
def start_with(*expected)
-
BuiltIn::StartWith.new(*expected)
-
end
-
1
alias_matcher :a_collection_starting_with, :start_with
-
1
alias_matcher :a_string_starting_with, :start_with
-
1
alias_matcher :starting_with, :start_with
-
-
# Given no argument, matches if a proc throws any Symbol.
-
#
-
# Given a Symbol, matches if the given proc throws the specified Symbol.
-
#
-
# Given a Symbol and an arg, matches if the given proc throws the
-
# specified Symbol with the specified arg.
-
#
-
# @example
-
# expect { do_something_risky }.to throw_symbol
-
# expect { do_something_risky }.to throw_symbol(:that_was_risky)
-
# expect { do_something_risky }.to throw_symbol(:that_was_risky, 'culprit')
-
#
-
# expect { do_something_risky }.not_to throw_symbol
-
# expect { do_something_risky }.not_to throw_symbol(:that_was_risky)
-
# expect { do_something_risky }.not_to throw_symbol(:that_was_risky, 'culprit')
-
1
def throw_symbol(expected_symbol=nil, expected_arg=nil)
-
BuiltIn::ThrowSymbol.new(expected_symbol, expected_arg)
-
end
-
-
1
alias_matcher :a_block_throwing, :throw_symbol do |desc|
-
desc.sub("throw", "a block throwing")
-
end
-
-
1
alias_matcher :throwing, :throw_symbol do |desc|
-
desc.sub("throw", "throwing")
-
end
-
-
# Passes if the method called in the expect block yields, regardless
-
# of whether or not arguments are yielded.
-
#
-
# @example
-
# expect { |b| 5.tap(&b) }.to yield_control
-
# expect { |b| "a".to_sym(&b) }.not_to yield_control
-
#
-
# @note Your expect block must accept a parameter and pass it on to
-
# the method-under-test as a block.
-
1
def yield_control
-
BuiltIn::YieldControl.new
-
end
-
1
alias_matcher :a_block_yielding_control, :yield_control
-
1
alias_matcher :yielding_control, :yield_control
-
-
# Passes if the method called in the expect block yields with
-
# no arguments. Fails if it does not yield, or yields with arguments.
-
#
-
# @example
-
# expect { |b| User.transaction(&b) }.to yield_with_no_args
-
# expect { |b| 5.tap(&b) }.not_to yield_with_no_args # because it yields with `5`
-
# expect { |b| "a".to_sym(&b) }.not_to yield_with_no_args # because it does not yield
-
#
-
# @note Your expect block must accept a parameter and pass it on to
-
# the method-under-test as a block.
-
# @note This matcher is not designed for use with methods that yield
-
# multiple times.
-
1
def yield_with_no_args
-
BuiltIn::YieldWithNoArgs.new
-
end
-
1
alias_matcher :a_block_yielding_with_no_args, :yield_with_no_args
-
1
alias_matcher :yielding_with_no_args, :yield_with_no_args
-
-
# Given no arguments, matches if the method called in the expect
-
# block yields with arguments (regardless of what they are or how
-
# many there are).
-
#
-
# Given arguments, matches if the method called in the expect block
-
# yields with arguments that match the given arguments.
-
#
-
# Argument matching is done using `===` (the case match operator)
-
# and `==`. If the expected and actual arguments match with either
-
# operator, the matcher will pass.
-
#
-
# @example
-
# expect { |b| 5.tap(&b) }.to yield_with_args # because #tap yields an arg
-
# expect { |b| 5.tap(&b) }.to yield_with_args(5) # because 5 == 5
-
# expect { |b| 5.tap(&b) }.to yield_with_args(Fixnum) # because Fixnum === 5
-
# expect { |b| File.open("f.txt", &b) }.to yield_with_args(/txt/) # because /txt/ === "f.txt"
-
#
-
# expect { |b| User.transaction(&b) }.not_to yield_with_args # because it yields no args
-
# expect { |b| 5.tap(&b) }.not_to yield_with_args(1, 2, 3)
-
#
-
# @note Your expect block must accept a parameter and pass it on to
-
# the method-under-test as a block.
-
# @note This matcher is not designed for use with methods that yield
-
# multiple times.
-
1
def yield_with_args(*args)
-
BuiltIn::YieldWithArgs.new(*args)
-
end
-
1
alias_matcher :a_block_yielding_with_args, :yield_with_args
-
1
alias_matcher :yielding_with_args, :yield_with_args
-
-
# Designed for use with methods that repeatedly yield (such as
-
# iterators). Passes if the method called in the expect block yields
-
# multiple times with arguments matching those given.
-
#
-
# Argument matching is done using `===` (the case match operator)
-
# and `==`. If the expected and actual arguments match with either
-
# operator, the matcher will pass.
-
#
-
# @example
-
# expect { |b| [1, 2, 3].each(&b) }.to yield_successive_args(1, 2, 3)
-
# expect { |b| { :a => 1, :b => 2 }.each(&b) }.to yield_successive_args([:a, 1], [:b, 2])
-
# expect { |b| [1, 2, 3].each(&b) }.not_to yield_successive_args(1, 2)
-
#
-
# @note Your expect block must accept a parameter and pass it on to
-
# the method-under-test as a block.
-
1
def yield_successive_args(*args)
-
BuiltIn::YieldSuccessiveArgs.new(*args)
-
end
-
1
alias_matcher :a_block_yielding_successive_args, :yield_successive_args
-
1
alias_matcher :yielding_successive_args, :yield_successive_args
-
-
# Delegates to {RSpec::Expectations.configuration}.
-
# This is here because rspec-core's `expect_with` option
-
# looks for a `configuration` method on the mixin
-
# (`RSpec::Matchers`) to yield to a block.
-
# @return [RSpec::Expectations::Configuration] the configuration object
-
1
def self.configuration
-
Expectations.configuration
-
end
-
-
1
private
-
-
1
BE_PREDICATE_REGEX = /^(be_(?:an?_)?)(.*)/
-
1
HAS_REGEX = /^(?:have_)(.*)/
-
1
DYNAMIC_MATCHER_REGEX = Regexp.union(BE_PREDICATE_REGEX, HAS_REGEX)
-
-
1
def method_missing(method, *args, &block)
-
case method.to_s
-
when BE_PREDICATE_REGEX
-
BuiltIn::BePredicate.new(method, *args, &block)
-
when HAS_REGEX
-
BuiltIn::Has.new(method, *args, &block)
-
else
-
super
-
end
-
end
-
-
1
if RUBY_VERSION.to_f >= 1.9
-
1
def respond_to_missing?(method, *)
-
method =~ DYNAMIC_MATCHER_REGEX || super
-
end
-
else # for 1.8.7
-
# :nocov:
-
skipped
def respond_to?(method, *)
-
skipped
method = method.to_s
-
skipped
method =~ DYNAMIC_MATCHER_REGEX || super
-
skipped
end
-
skipped
public :respond_to?
-
# :nocov:
-
end
-
-
# @api private
-
1
def self.is_a_matcher?(obj)
-
5
return true if ::RSpec::Matchers::BuiltIn::BaseMatcher === obj
-
5
begin
-
5
return false if obj.respond_to?(:i_respond_to_everything_so_im_not_really_a_matcher)
-
rescue NoMethodError
-
# Some objects, like BasicObject, don't implemented standard
-
# reflection methods.
-
return false
-
end
-
5
return false unless obj.respond_to?(:matches?)
-
-
obj.respond_to?(:failure_message) ||
-
obj.respond_to?(:failure_message_for_should) # support legacy matchers
-
end
-
-
1
::RSpec::Support.register_matcher_definition do |obj|
-
is_a_matcher?(obj)
-
end
-
-
# @api private
-
1
def self.is_a_describable_matcher?(obj)
-
is_a_matcher?(obj) && obj.respond_to?(:description)
-
end
-
-
1
if RSpec::Support::Ruby.mri? && RUBY_VERSION[0, 3] == '1.9'
-
# @api private
-
# Note that `included` doesn't work for this because it is triggered
-
# _after_ `RSpec::Matchers` is an ancestor of the inclusion host, rather
-
# than _before_, like `append_features`. It's important we check this before
-
# in order to find the cases where it was already previously included.
-
def self.append_features(mod)
-
return super if mod < self # `mod < self` indicates a re-inclusion.
-
-
subclasses = ObjectSpace.each_object(Class).select { |c| c < mod && c < self }
-
return super unless subclasses.any?
-
-
subclasses.reject! { |s| subclasses.any? { |s2| s < s2 } } # Filter to the root ancestor.
-
subclasses = subclasses.map { |s| "`#{s}`" }.join(", ")
-
-
RSpec.warning "`#{self}` has been included in a superclass (`#{mod}`) " \
-
"after previously being included in subclasses (#{subclasses}), " \
-
"which can trigger infinite recursion from `super` due to an MRI 1.9 bug " \
-
"(https://redmine.ruby-lang.org/issues/3351). To work around this, " \
-
"either upgrade to MRI 2.0+, include a dup of the module (e.g. " \
-
"`include #{self}.dup`), or find a way to include `#{self}` in `#{mod}` " \
-
"before it is included in subclasses (#{subclasses}). See " \
-
"https://github.com/rspec/rspec-expectations/issues/814 for more info"
-
-
super
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
# Decorator that wraps a matcher and overrides `description`
-
# using the provided block in order to support an alias
-
# of a matcher. This is intended for use when composing
-
# matchers, so that you can use an expression like
-
# `include( a_value_within(0.1).of(3) )` rather than
-
# `include( be_within(0.1).of(3) )`, and have the corresponding
-
# description read naturally.
-
#
-
# @api private
-
1
class AliasedMatcher < MatcherDelegator
-
1
def initialize(base_matcher, description_block)
-
@description_block = description_block
-
super(base_matcher)
-
end
-
-
# Forward messages on to the wrapped matcher.
-
# Since many matchers provide a fluent interface
-
# (e.g. `a_value_within(0.1).of(3)`), we need to wrap
-
# the returned value if it responds to `description`,
-
# so that our override can be applied when it is eventually
-
# used.
-
1
def method_missing(*)
-
return_val = super
-
return return_val unless RSpec::Matchers.is_a_matcher?(return_val)
-
self.class.new(return_val, @description_block)
-
end
-
-
# Provides the description of the aliased matcher. Aliased matchers
-
# are designed to behave identically to the original matcher except
-
# for the description and failure messages. The description is different
-
# to reflect the aliased name.
-
#
-
# @api private
-
1
def description
-
@description_block.call(super)
-
end
-
-
# Provides the failure_message of the aliased matcher. Aliased matchers
-
# are designed to behave identically to the original matcher except
-
# for the description and failure messages. The failure_message is different
-
# to reflect the aliased name.
-
#
-
# @api private
-
1
def failure_message
-
@description_block.call(super)
-
end
-
-
# Provides the failure_message_when_negated of the aliased matcher. Aliased matchers
-
# are designed to behave identically to the original matcher except
-
# for the description and failure messages. The failure_message_when_negated is different
-
# to reflect the aliased name.
-
#
-
# @api private
-
1
def failure_message_when_negated
-
@description_block.call(super)
-
end
-
end
-
-
# Decorator used for matchers that have special implementations of
-
# operators like `==` and `===`.
-
# @private
-
1
class AliasedMatcherWithOperatorSupport < AliasedMatcher
-
# We undef these so that they get delegated via `method_missing`.
-
1
undef ==
-
1
undef ===
-
end
-
-
# @private
-
1
class AliasedNegatedMatcher < AliasedMatcher
-
1
def matches?(*args, &block)
-
if @base_matcher.respond_to?(:does_not_match?)
-
@base_matcher.does_not_match?(*args, &block)
-
else
-
!super
-
end
-
end
-
-
1
def does_not_match?(*args, &block)
-
@base_matcher.matches?(*args, &block)
-
end
-
-
1
def failure_message
-
optimal_failure_message(__method__, :failure_message_when_negated)
-
end
-
-
1
def failure_message_when_negated
-
optimal_failure_message(__method__, :failure_message)
-
end
-
-
1
private
-
-
1
DefaultFailureMessages = BuiltIn::BaseMatcher::DefaultFailureMessages
-
-
# For a matcher that uses the default failure messages, we prefer to
-
# use the override provided by the `description_block`, because it
-
# includes the phrasing that the user has expressed a preference for
-
# by going through the effort of defining a negated matcher.
-
#
-
# However, if the override didn't actually change anything, then we
-
# should return the opposite failure message instead -- the overriden
-
# message is going to be confusing if we return it as-is, as it represents
-
# the non-negated failure message for a negated match (or vice versa).
-
1
def optimal_failure_message(same, inverted)
-
if DefaultFailureMessages.has_default_failure_messages?(@base_matcher)
-
base_message = @base_matcher.__send__(same)
-
overriden = @description_block.call(base_message)
-
return overriden if overriden != base_message
-
end
-
-
@base_matcher.__send__(inverted)
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_matchers "built_in/base_matcher"
-
-
1
module RSpec
-
1
module Matchers
-
# Container module for all built-in matchers. The matcher classes are here
-
# (rather than directly under `RSpec::Matchers`) in order to prevent name
-
# collisions, since `RSpec::Matchers` gets included into the user's namespace.
-
#
-
# Autoloading is used to delay when the matcher classes get loaded, allowing
-
# rspec-matchers to boot faster, and avoiding loading matchers the user is
-
# not using.
-
1
module BuiltIn
-
1
autoload :BeAKindOf, 'rspec/matchers/built_in/be_kind_of'
-
1
autoload :BeAnInstanceOf, 'rspec/matchers/built_in/be_instance_of'
-
1
autoload :BeBetween, 'rspec/matchers/built_in/be_between'
-
1
autoload :Be, 'rspec/matchers/built_in/be'
-
1
autoload :BeComparedTo, 'rspec/matchers/built_in/be'
-
1
autoload :BeFalsey, 'rspec/matchers/built_in/be'
-
1
autoload :BeNil, 'rspec/matchers/built_in/be'
-
1
autoload :BePredicate, 'rspec/matchers/built_in/be'
-
1
autoload :BeTruthy, 'rspec/matchers/built_in/be'
-
1
autoload :BeWithin, 'rspec/matchers/built_in/be_within'
-
1
autoload :Change, 'rspec/matchers/built_in/change'
-
1
autoload :Compound, 'rspec/matchers/built_in/compound'
-
1
autoload :ContainExactly, 'rspec/matchers/built_in/contain_exactly'
-
1
autoload :Cover, 'rspec/matchers/built_in/cover'
-
1
autoload :EndWith, 'rspec/matchers/built_in/start_or_end_with'
-
1
autoload :Eq, 'rspec/matchers/built_in/eq'
-
1
autoload :Eql, 'rspec/matchers/built_in/eql'
-
1
autoload :Equal, 'rspec/matchers/built_in/equal'
-
1
autoload :Exist, 'rspec/matchers/built_in/exist'
-
1
autoload :Has, 'rspec/matchers/built_in/has'
-
1
autoload :HaveAttributes, 'rspec/matchers/built_in/have_attributes'
-
1
autoload :Include, 'rspec/matchers/built_in/include'
-
1
autoload :All, 'rspec/matchers/built_in/all'
-
1
autoload :Match, 'rspec/matchers/built_in/match'
-
1
autoload :NegativeOperatorMatcher, 'rspec/matchers/built_in/operators'
-
1
autoload :OperatorMatcher, 'rspec/matchers/built_in/operators'
-
1
autoload :Output, 'rspec/matchers/built_in/output'
-
1
autoload :PositiveOperatorMatcher, 'rspec/matchers/built_in/operators'
-
1
autoload :RaiseError, 'rspec/matchers/built_in/raise_error'
-
1
autoload :RespondTo, 'rspec/matchers/built_in/respond_to'
-
1
autoload :Satisfy, 'rspec/matchers/built_in/satisfy'
-
1
autoload :StartWith, 'rspec/matchers/built_in/start_or_end_with'
-
1
autoload :ThrowSymbol, 'rspec/matchers/built_in/throw_symbol'
-
1
autoload :YieldControl, 'rspec/matchers/built_in/yield'
-
1
autoload :YieldSuccessiveArgs, 'rspec/matchers/built_in/yield'
-
1
autoload :YieldWithArgs, 'rspec/matchers/built_in/yield'
-
1
autoload :YieldWithNoArgs, 'rspec/matchers/built_in/yield'
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
#
-
# Used _internally_ as a base class for matchers that ship with
-
# rspec-expectations and rspec-rails.
-
#
-
# ### Warning:
-
#
-
# This class is for internal use, and subject to change without notice.
-
# We strongly recommend that you do not base your custom matchers on this
-
# class. If/when this changes, we will announce it and remove this warning.
-
1
class BaseMatcher
-
1
include RSpec::Matchers::Composable
-
-
# @api private
-
# Used to detect when no arg is passed to `initialize`.
-
# `nil` cannot be used because it's a valid value to pass.
-
1
UNDEFINED = Object.new.freeze
-
-
# @private
-
1
attr_reader :actual, :expected, :rescued_exception
-
-
1
def initialize(expected=UNDEFINED)
-
5
@expected = expected unless UNDEFINED.equal?(expected)
-
end
-
-
# @api private
-
# Indicates if the match is successful. Delegates to `match`, which
-
# should be defined on a subclass. Takes care of consistently
-
# initializing the `actual` attribute.
-
1
def matches?(actual)
-
5
@actual = actual
-
5
match(expected, actual)
-
end
-
-
# @api private
-
# Used to wrap a block of code that will indicate failure by
-
# raising one of the named exceptions.
-
#
-
# This is used by rspec-rails for some of its matchers that
-
# wrap rails' assertions.
-
1
def match_unless_raises(*exceptions)
-
exceptions.unshift Exception if exceptions.empty?
-
begin
-
yield
-
true
-
rescue *exceptions => @rescued_exception
-
false
-
end
-
end
-
-
# @api private
-
# Generates a description using {EnglishPhrasing}.
-
# @return [String]
-
1
def description
-
desc = EnglishPhrasing.split_words(self.class.matcher_name)
-
desc << EnglishPhrasing.list(@expected) if defined?(@expected)
-
desc
-
end
-
-
# @api private
-
# Matchers are not diffable by default. Override this to make your
-
# subclass diffable.
-
1
def diffable?
-
false
-
end
-
-
# @api private
-
# Most matchers are value matchers (i.e. meant to work with `expect(value)`)
-
# rather than block matchers (i.e. meant to work with `expect { }`), so
-
# this defaults to false. Block matchers must override this to return true.
-
1
def supports_block_expectations?
-
false
-
end
-
-
# @api private
-
1
def expects_call_stack_jump?
-
false
-
end
-
-
# @private
-
1
def expected_formatted
-
RSpec::Support::ObjectFormatter.format(@expected)
-
end
-
-
# @private
-
1
def actual_formatted
-
RSpec::Support::ObjectFormatter.format(@actual)
-
end
-
-
# @private
-
1
def self.matcher_name
-
@matcher_name ||= underscore(name.split('::').last)
-
end
-
-
# @private
-
# Borrowed from ActiveSupport.
-
1
def self.underscore(camel_cased_word)
-
word = camel_cased_word.to_s.dup
-
word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
-
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
-
word.tr!('-', '_')
-
word.downcase!
-
word
-
end
-
1
private_class_method :underscore
-
-
1
private
-
-
1
def assert_ivars(*expected_ivars)
-
return unless (expected_ivars - present_ivars).any?
-
ivar_list = EnglishPhrasing.list(expected_ivars)
-
raise "#{self.class.name} needs to supply#{ivar_list}"
-
end
-
-
1
if RUBY_VERSION.to_f < 1.9
-
# :nocov:
-
skipped
def present_ivars
-
skipped
instance_variables.map(&:to_sym)
-
skipped
end
-
# :nocov:
-
else
-
1
alias present_ivars instance_variables
-
end
-
-
# @private
-
1
module HashFormatting
-
# `{ :a => 5, :b => 2 }.inspect` produces:
-
#
-
# {:a=>5, :b=>2}
-
#
-
# ...but it looks much better as:
-
#
-
# {:a => 5, :b => 2}
-
#
-
# This is idempotent and safe to run on a string multiple times.
-
1
def improve_hash_formatting(inspect_string)
-
inspect_string.gsub(/(\S)=>(\S)/, '\1 => \2')
-
end
-
1
module_function :improve_hash_formatting
-
end
-
-
1
include HashFormatting
-
-
# @api private
-
# Provides default implementations of failure messages, based on the `description`.
-
1
module DefaultFailureMessages
-
# @api private
-
# Provides a good generic failure message. Based on `description`.
-
# When subclassing, if you are not satisfied with this failure message
-
# you often only need to override `description`.
-
# @return [String]
-
1
def failure_message
-
"expected #{description_of @actual} to #{description}"
-
end
-
-
# @api private
-
# Provides a good generic negative failure message. Based on `description`.
-
# When subclassing, if you are not satisfied with this failure message
-
# you often only need to override `description`.
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected #{description_of @actual} not to #{description}"
-
end
-
-
# @private
-
1
def self.has_default_failure_messages?(matcher)
-
matcher.method(:failure_message).owner == self &&
-
matcher.method(:failure_message_when_negated).owner == self
-
rescue NameError
-
false
-
end
-
end
-
-
1
include DefaultFailureMessages
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `be_truthy`.
-
# Not intended to be instantiated directly.
-
1
class BeTruthy < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected: truthy value\n got: #{actual_formatted}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected: falsey value\n got: #{actual_formatted}"
-
end
-
-
1
private
-
-
1
def match(_, actual)
-
!!actual
-
end
-
end
-
-
# @api private
-
# Provides the implementation for `be_falsey`.
-
# Not intended to be instantiated directly.
-
1
class BeFalsey < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected: falsey value\n got: #{actual_formatted}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected: truthy value\n got: #{actual_formatted}"
-
end
-
-
1
private
-
-
1
def match(_, actual)
-
!actual
-
end
-
end
-
-
# @api private
-
# Provides the implementation for `be_nil`.
-
# Not intended to be instantiated directly.
-
1
class BeNil < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected: nil\n got: #{actual_formatted}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected: not nil\n got: nil"
-
end
-
-
1
private
-
-
1
def match(_, actual)
-
1
actual.nil?
-
end
-
end
-
-
# @private
-
1
module BeHelpers
-
1
private
-
-
1
def args_to_s
-
@args.empty? ? "" : parenthesize(inspected_args.join(', '))
-
end
-
-
1
def parenthesize(string)
-
"(#{string})"
-
end
-
-
1
def inspected_args
-
@args.map { |a| RSpec::Support::ObjectFormatter.format(a) }
-
end
-
-
1
def expected_to_sentence
-
EnglishPhrasing.split_words(@expected)
-
end
-
-
1
def args_to_sentence
-
EnglishPhrasing.list(@args)
-
end
-
end
-
-
# @api private
-
# Provides the implementation for `be`.
-
# Not intended to be instantiated directly.
-
1
class Be < BaseMatcher
-
1
include BeHelpers
-
-
1
def initialize(*args)
-
@args = args
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected #{actual_formatted} to evaluate to true"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected #{actual_formatted} to evaluate to false"
-
end
-
-
1
[:==, :<, :<=, :>=, :>, :===, :=~].each do |operator|
-
7
define_method operator do |operand|
-
BeComparedTo.new(operand, operator)
-
end
-
end
-
-
1
private
-
-
1
def match(_, actual)
-
!!actual
-
end
-
end
-
-
# @api private
-
# Provides the implementation of `be <operator> value`.
-
# Not intended to be instantiated directly.
-
1
class BeComparedTo < BaseMatcher
-
1
include BeHelpers
-
-
1
def initialize(operand, operator)
-
@expected, @operator = operand, operator
-
@args = []
-
end
-
-
1
def matches?(actual)
-
@actual = actual
-
@actual.__send__ @operator, @expected
-
rescue ArgumentError
-
false
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected: #{@operator} #{expected_formatted}\n got: #{@operator.to_s.gsub(/./, ' ')} #{actual_formatted}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
message = "`expect(#{actual_formatted}).not_to be #{@operator} #{expected_formatted}`"
-
if [:<, :>, :<=, :>=].include?(@operator)
-
message + " not only FAILED, it is a bit confusing."
-
else
-
message
-
end
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
"be #{@operator} #{expected_to_sentence}#{args_to_sentence}"
-
end
-
end
-
-
# @api private
-
# Provides the implementation of `be_<predicate>`.
-
# Not intended to be instantiated directly.
-
1
class BePredicate < BaseMatcher
-
1
include BeHelpers
-
-
1
def initialize(*args, &block)
-
@expected = parse_expected(args.shift)
-
@args = args
-
@block = block
-
end
-
-
1
def matches?(actual, &block)
-
@actual = actual
-
@block ||= block
-
predicate_accessible? && predicate_matches?
-
end
-
-
1
def does_not_match?(actual, &block)
-
@actual = actual
-
@block ||= block
-
predicate_accessible? && !predicate_matches?
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message
-
failure_message_expecting(true)
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
failure_message_expecting(false)
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
"#{prefix_to_sentence}#{expected_to_sentence}#{args_to_sentence}"
-
end
-
-
1
private
-
-
1
def predicate_accessible?
-
actual.respond_to?(predicate) || actual.respond_to?(present_tense_predicate)
-
end
-
-
# support 1.8.7, evaluate once at load time for performance
-
1
if String === methods.first
-
# :nocov:
-
skipped
def private_predicate?
-
skipped
@actual.private_methods.include? predicate.to_s
-
skipped
end
-
# :nocov:
-
else
-
1
def private_predicate?
-
@actual.private_methods.include? predicate
-
end
-
end
-
-
1
def predicate_matches?
-
method_name = actual.respond_to?(predicate) ? predicate : present_tense_predicate
-
@predicate_matches = actual.__send__(method_name, *@args, &@block)
-
end
-
-
1
def predicate
-
:"#{@expected}?"
-
end
-
-
1
def present_tense_predicate
-
:"#{@expected}s?"
-
end
-
-
1
def parse_expected(expected)
-
@prefix, expected = prefix_and_expected(expected)
-
expected
-
end
-
-
1
def prefix_and_expected(symbol)
-
Matchers::BE_PREDICATE_REGEX.match(symbol.to_s).captures.compact
-
end
-
-
1
def prefix_to_sentence
-
EnglishPhrasing.split_words(@prefix)
-
end
-
-
1
def failure_message_expecting(value)
-
validity_message ||
-
"expected `#{actual_formatted}.#{predicate}#{args_to_s}` to return #{value}, got #{description_of @predicate_matches}"
-
end
-
-
1
def validity_message
-
return nil if predicate_accessible?
-
-
msg = "expected #{actual_formatted} to respond to `#{predicate}`"
-
-
if private_predicate?
-
msg << " but `#{predicate}` is a private method"
-
elsif predicate == :true?
-
msg << " or perhaps you meant `be true` or `be_truthy`"
-
elsif predicate == :false?
-
msg << " or perhaps you meant `be false` or `be_falsey`"
-
end
-
-
msg
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `eq`.
-
# Not intended to be instantiated directly.
-
1
class Eq < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"\nexpected: #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using ==)\n"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"\nexpected: value != #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using ==)\n"
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
"eq #{expected_formatted}"
-
end
-
-
# @api private
-
# @return [Boolean]
-
1
def diffable?
-
true
-
end
-
-
1
private
-
-
1
def match(expected, actual)
-
3
actual == expected
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `equal`.
-
# Not intended to be instantiated directly.
-
1
class Equal < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def failure_message
-
if expected_is_a_literal_singleton?
-
simple_failure_message
-
else
-
detailed_failure_message
-
end
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
<<-MESSAGE
-
-
expected not #{inspect_object(actual)}
-
got #{inspect_object(expected)}
-
-
Compared using equal?, which compares object identity.
-
-
MESSAGE
-
end
-
-
# @api private
-
# @return [Boolean]
-
1
def diffable?
-
!expected_is_a_literal_singleton?
-
end
-
-
1
private
-
-
1
def match(expected, actual)
-
1
actual.equal? expected
-
end
-
-
1
LITERAL_SINGLETONS = [true, false, nil]
-
-
1
def expected_is_a_literal_singleton?
-
LITERAL_SINGLETONS.include?(expected)
-
end
-
-
1
def actual_inspected
-
if LITERAL_SINGLETONS.include?(actual)
-
actual_formatted
-
else
-
inspect_object(actual)
-
end
-
end
-
-
1
def simple_failure_message
-
"\nexpected #{expected_formatted}\n got #{actual_inspected}\n"
-
end
-
-
1
def detailed_failure_message
-
<<-MESSAGE
-
-
expected #{inspect_object(expected)}
-
got #{inspect_object(actual)}
-
-
Compared using equal?, which compares object identity,
-
but expected and actual are not the same object. Use
-
`expect(actual).to eq(expected)` if you don't care about
-
object identity in this example.
-
-
MESSAGE
-
end
-
-
1
def inspect_object(o)
-
"#<#{o.class}:#{o.object_id}> => #{RSpec::Support::ObjectFormatter.format(o)}"
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `raise_error`.
-
# Not intended to be instantiated directly.
-
# rubocop:disable ClassLength
-
# rubocop:disable RescueException
-
1
class RaiseError
-
1
include Composable
-
-
1
def initialize(expected_error_or_message=nil, expected_message=nil, &block)
-
4
@block = block
-
4
@actual_error = nil
-
4
@warn_about_bare_error = warn_about_potential_false_positives? && expected_error_or_message.nil?
-
-
4
case expected_error_or_message
-
when nil
-
@expected_error = Exception
-
@expected_message = expected_message
-
when String
-
@expected_error = Exception
-
@expected_message = expected_error_or_message
-
else
-
4
@expected_error = expected_error_or_message
-
4
@expected_message = expected_message
-
end
-
end
-
-
# @api public
-
# Specifies the expected error message.
-
1
def with_message(expected_message)
-
raise_message_already_set if @expected_message
-
@warn_about_bare_error = false
-
@expected_message = expected_message
-
self
-
end
-
-
# rubocop:disable MethodLength
-
# @private
-
1
def matches?(given_proc, negative_expectation=false, &block)
-
4
@given_proc = given_proc
-
4
@block ||= block
-
4
@raised_expected_error = false
-
4
@with_expected_message = false
-
4
@eval_block = false
-
4
@eval_block_passed = false
-
-
4
return false unless Proc === given_proc
-
-
4
begin
-
4
given_proc.call
-
rescue Exception => @actual_error
-
if values_match?(@expected_error, @actual_error) ||
-
4
values_match?(@expected_error, @actual_error.message)
-
4
@raised_expected_error = true
-
4
@with_expected_message = verify_message
-
end
-
end
-
-
4
warn_about_bare_error if warning_about_bare_error && !negative_expectation
-
4
eval_block if !negative_expectation && ready_to_eval_block?
-
-
4
expectation_matched?
-
end
-
# rubocop:enable MethodLength
-
-
# @private
-
1
def does_not_match?(given_proc)
-
warn_for_false_positives
-
!matches?(given_proc, :negative_expectation) && Proc === given_proc
-
end
-
-
# @private
-
1
def supports_block_expectations?
-
4
true
-
end
-
-
1
def expects_call_stack_jump?
-
true
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message
-
@eval_block ? @actual_error.message : "expected #{expected_error}#{given_error}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected no #{expected_error}#{given_error}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
"raise #{expected_error}"
-
end
-
-
1
private
-
-
1
def expectation_matched?
-
4
error_and_message_match? && block_matches?
-
end
-
-
1
def error_and_message_match?
-
4
@raised_expected_error && @with_expected_message
-
end
-
-
1
def block_matches?
-
4
@eval_block ? @eval_block_passed : true
-
end
-
-
1
def ready_to_eval_block?
-
4
@raised_expected_error && @with_expected_message && @block
-
end
-
-
1
def eval_block
-
@eval_block = true
-
begin
-
@block[@actual_error]
-
@eval_block_passed = true
-
rescue Exception => err
-
@actual_error = err
-
end
-
end
-
-
1
def verify_message
-
4
return true if @expected_message.nil?
-
1
values_match?(@expected_message, @actual_error.message.to_s)
-
end
-
-
1
def warn_for_false_positives
-
return unless warn_about_potential_false_positives?
-
expression = if expecting_specific_exception? && @expected_message
-
"`expect { }.not_to raise_error(SpecificErrorClass, message)`"
-
elsif expecting_specific_exception?
-
"`expect { }.not_to raise_error(SpecificErrorClass)`"
-
elsif @expected_message
-
"`expect { }.not_to raise_error(message)`"
-
end
-
-
return unless expression
-
-
warn_about_negative_false_positive expression
-
end
-
-
1
def warn_about_potential_false_positives?
-
4
RSpec::Expectations.configuration.warn_about_potential_false_positives?
-
end
-
-
1
def warning_about_bare_error
-
4
@warn_about_bare_error && @block.nil?
-
end
-
-
1
def warn_about_bare_error
-
RSpec.warning("Using the `raise_error` matcher without providing a specific " \
-
"error or message risks false positives, since `raise_error` " \
-
"will match when Ruby raises a `NoMethodError`, `NameError` or " \
-
"`ArgumentError`, potentially allowing the expectation to pass " \
-
"without even executing the method you are intending to call. " \
-
"#{warning}"\
-
"Instead consider providing a specific error class or message. " \
-
"This message can be supressed by setting: " \
-
"`RSpec::Expectations.configuration.warn_about_potential_false_positives = false`")
-
end
-
-
1
def warn_about_negative_false_positive(expression)
-
RSpec.warning("Using #{expression} risks false positives, since literally " \
-
"any other error would cause the expectation to pass, " \
-
"including those raised by Ruby (e.g. NoMethodError, NameError " \
-
"and ArgumentError), meaning the code you are intending to test " \
-
"may not even get reached. Instead consider using " \
-
"`expect {}.not_to raise_error`. This message can be supressed by setting: " \
-
"`RSpec::Expectations.configuration.warn_about_potential_false_positives = false`")
-
end
-
-
1
def expected_error
-
case @expected_message
-
when nil
-
if RSpec::Support.is_a_matcher?(@expected_error)
-
"Exception with #{description_of(@expected_error)}"
-
else
-
description_of(@expected_error)
-
end
-
when Regexp
-
"#{@expected_error} with message matching #{description_of(@expected_message)}"
-
else
-
"#{@expected_error} with #{description_of(@expected_message)}"
-
end
-
end
-
-
1
def format_backtrace(backtrace)
-
formatter = Matchers.configuration.backtrace_formatter
-
formatter.format_backtrace(backtrace)
-
end
-
-
1
def given_error
-
return " but was not given a block" unless Proc === @given_proc
-
return " but nothing was raised" unless @actual_error
-
-
backtrace = format_backtrace(@actual_error.backtrace)
-
[
-
", got #{description_of(@actual_error)} with backtrace:",
-
*backtrace
-
].join("\n # ")
-
end
-
-
1
def expecting_specific_exception?
-
@expected_error != Exception
-
end
-
-
1
def raise_message_already_set
-
raise "`expect { }.to raise_error(message).with_message(message)` is not valid. " \
-
'The matcher only allows the expected message to be specified once'
-
end
-
-
1
def warning
-
warning = "Actual error raised was #{description_of(@actual_error)}. "
-
warning if @actual_error
-
end
-
end
-
# rubocop:enable RescueException
-
# rubocop:enable ClassLength
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support "fuzzy_matcher"
-
-
1
module RSpec
-
1
module Matchers
-
# Mixin designed to support the composable matcher features
-
# of RSpec 3+. Mix it into your custom matcher classes to
-
# allow them to be used in a composable fashion.
-
#
-
# @api public
-
1
module Composable
-
# Creates a compound `and` expectation. The matcher will
-
# only pass if both sub-matchers pass.
-
# This can be chained together to form an arbitrarily long
-
# chain of matchers.
-
#
-
# @example
-
# expect(alphabet).to start_with("a").and end_with("z")
-
# expect(alphabet).to start_with("a") & end_with("z")
-
#
-
# @note The negative form (`expect(...).not_to matcher.and other`)
-
# is not supported at this time.
-
1
def and(matcher)
-
BuiltIn::Compound::And.new self, matcher
-
end
-
1
alias & and
-
-
# Creates a compound `or` expectation. The matcher will
-
# pass if either sub-matcher passes.
-
# This can be chained together to form an arbitrarily long
-
# chain of matchers.
-
#
-
# @example
-
# expect(stoplight.color).to eq("red").or eq("green").or eq("yellow")
-
# expect(stoplight.color).to eq("red") | eq("green") | eq("yellow")
-
#
-
# @note The negative form (`expect(...).not_to matcher.or other`)
-
# is not supported at this time.
-
1
def or(matcher)
-
BuiltIn::Compound::Or.new self, matcher
-
end
-
1
alias | or
-
-
# Delegates to `#matches?`. Allows matchers to be used in composable
-
# fashion and also supports using matchers in case statements.
-
1
def ===(value)
-
matches?(value)
-
end
-
-
1
private
-
-
# This provides a generic way to fuzzy-match an expected value against
-
# an actual value. It understands nested data structures (e.g. hashes
-
# and arrays) and is able to match against a matcher being used as
-
# the expected value or within the expected value at any level of
-
# nesting.
-
#
-
# Within a custom matcher you are encouraged to use this whenever your
-
# matcher needs to match two values, unless it needs more precise semantics.
-
# For example, the `eq` matcher _does not_ use this as it is meant to
-
# use `==` (and only `==`) for matching.
-
#
-
# @param expected [Object] what is expected
-
# @param actual [Object] the actual value
-
#
-
# @!visibility public
-
1
def values_match?(expected, actual)
-
5
expected = with_matchers_cloned(expected)
-
5
Support::FuzzyMatcher.values_match?(expected, actual)
-
end
-
-
# Returns the description of the given object in a way that is
-
# aware of composed matchers. If the object is a matcher with
-
# a `description` method, returns the description; otherwise
-
# returns `object.inspect`.
-
#
-
# You are encouraged to use this in your custom matcher's
-
# `description`, `failure_message` or
-
# `failure_message_when_negated` implementation if you are
-
# supporting matcher arguments.
-
#
-
# @!visibility public
-
1
def description_of(object)
-
RSpec::Support::ObjectFormatter.format(object)
-
end
-
-
# Transforms the given data structue (typically a hash or array)
-
# into a new data structure that, when `#inspect` is called on it,
-
# will provide descriptions of any contained matchers rather than
-
# the normal `#inspect` output.
-
#
-
# You are encouraged to use this in your custom matcher's
-
# `description`, `failure_message` or
-
# `failure_message_when_negated` implementation if you are
-
# supporting any arguments which may be a data structure
-
# containing matchers.
-
#
-
# @!visibility public
-
1
def surface_descriptions_in(item)
-
if Matchers.is_a_describable_matcher?(item)
-
DescribableItem.new(item)
-
elsif Hash === item
-
Hash[surface_descriptions_in(item.to_a)]
-
elsif Struct === item || unreadable_io?(item)
-
RSpec::Support::ObjectFormatter.format(item)
-
elsif should_enumerate?(item)
-
item.map { |subitem| surface_descriptions_in(subitem) }
-
else
-
item
-
end
-
end
-
-
# @private
-
# Historically, a single matcher instance was only checked
-
# against a single value. Given that the matcher was only
-
# used once, it's been common to memoize some intermediate
-
# calculation that is derived from the `actual` value in
-
# order to reuse that intermediate result in the failure
-
# message.
-
#
-
# This can cause a problem when using such a matcher as an
-
# argument to another matcher in a composed matcher expression,
-
# since the matcher instance may be checked against multiple
-
# values and produce invalid results due to the memoization.
-
#
-
# To deal with this, we clone any matchers in `expected` via
-
# this method when using `values_match?`, so that any memoization
-
# does not "leak" between checks.
-
1
def with_matchers_cloned(object)
-
5
if Matchers.is_a_matcher?(object)
-
object.clone
-
5
elsif Hash === object
-
Hash[with_matchers_cloned(object.to_a)]
-
5
elsif Struct === object || unreadable_io?(object)
-
object
-
5
elsif should_enumerate?(object)
-
object.map { |subobject| with_matchers_cloned(subobject) }
-
else
-
5
object
-
end
-
end
-
-
1
if String.ancestors.include?(Enumerable) # 1.8.7
-
# :nocov:
-
skipped
# Strings are not enumerable on 1.9, and on 1.8 they are an infinitely
-
skipped
# nested enumerable: since ruby lacks a character class, it yields
-
skipped
# 1-character strings, which are themselves enumerable, composed of a
-
skipped
# a single 1-character string, which is an enumerable, etc.
-
skipped
#
-
skipped
# @api private
-
skipped
def should_enumerate?(item)
-
skipped
return false if String === item
-
skipped
Enumerable === item && !(Range === item) && item.none? { |subitem| subitem.equal?(item) }
-
skipped
end
-
# :nocov:
-
else
-
# @api private
-
1
def should_enumerate?(item)
-
5
Enumerable === item && !(Range === item) && item.none? { |subitem| subitem.equal?(item) }
-
end
-
end
-
-
# @api private
-
1
def unreadable_io?(object)
-
5
return false unless IO === object
-
object.each {} # STDOUT is enumerable but raises an error
-
false
-
rescue IOError
-
true
-
end
-
1
module_function :surface_descriptions_in, :should_enumerate?, :unreadable_io?
-
-
# Wraps an item in order to surface its `description` via `inspect`.
-
# @api private
-
1
DescribableItem = Struct.new(:item) do
-
1
def inspect
-
"(#{item.description})"
-
end
-
-
1
def pretty_print(pp)
-
pp.text "(#{item.description})"
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
# Defines the custom matcher DSL.
-
1
module DSL
-
# Defines a custom matcher.
-
# @see RSpec::Matchers
-
1
def define(name, &declarations)
-
warn_about_block_args(name, declarations)
-
define_method name do |*expected, &block_arg|
-
RSpec::Matchers::DSL::Matcher.new(name, declarations, self, *expected, &block_arg)
-
end
-
end
-
1
alias_method :matcher, :define
-
-
1
private
-
-
1
if Proc.method_defined?(:parameters)
-
1
def warn_about_block_args(name, declarations)
-
declarations.parameters.each do |type, arg_name|
-
next unless type == :block
-
RSpec.warning("Your `#{name}` custom matcher receives a block argument (`#{arg_name}`), " \
-
"but due to limitations in ruby, RSpec cannot provide the block. Instead, " \
-
"use the `block_arg` method to access the block")
-
end
-
end
-
else
-
# :nocov:
-
skipped
def warn_about_block_args(*)
-
skipped
# There's no way to detect block params on 1.8 since the method reflection APIs don't expose it
-
skipped
end
-
# :nocov:
-
end
-
-
2
RSpec.configure { |c| c.extend self } if RSpec.respond_to?(:configure)
-
-
# Contains the methods that are available from within the
-
# `RSpec::Matchers.define` DSL for creating custom matchers.
-
1
module Macros
-
# Stores the block that is used to determine whether this matcher passes
-
# or fails. The block should return a boolean value. When the matcher is
-
# passed to `expect(...).to` and the block returns `true`, then the expectation
-
# passes. Similarly, when the matcher is passed to `expect(...).not_to` and the
-
# block returns `false`, then the expectation passes.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :be_even do
-
# match do |actual|
-
# actual.even?
-
# end
-
# end
-
#
-
# expect(4).to be_even # passes
-
# expect(3).not_to be_even # passes
-
# expect(3).to be_even # fails
-
# expect(4).not_to be_even # fails
-
#
-
# @yield [Object] actual the actual value (i.e. the value wrapped by `expect`)
-
1
def match(&match_block)
-
define_user_override(:matches?, match_block) do |actual|
-
begin
-
@actual = actual
-
RSpec::Support.with_failure_notifier(RAISE_NOTIFIER) do
-
super(*actual_arg_for(match_block))
-
end
-
rescue RSpec::Expectations::ExpectationNotMetError
-
false
-
end
-
end
-
end
-
-
# @private
-
1
RAISE_NOTIFIER = Proc.new { |err, _opts| raise err }
-
-
# Use this to define the block for a negative expectation (`expect(...).not_to`)
-
# when the positive and negative forms require different handling. This
-
# is rarely necessary, but can be helpful, for example, when specifying
-
# asynchronous processes that require different timeouts.
-
#
-
# @yield [Object] actual the actual value (i.e. the value wrapped by `expect`)
-
1
def match_when_negated(&match_block)
-
define_user_override(:does_not_match?, match_block) do |actual|
-
@actual = actual
-
super(*actual_arg_for(match_block))
-
end
-
end
-
-
# Use this instead of `match` when the block will raise an exception
-
# rather than returning false to indicate a failure.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :accept_as_valid do |candidate_address|
-
# match_unless_raises ValidationException do |validator|
-
# validator.validate(candidate_address)
-
# end
-
# end
-
#
-
# expect(email_validator).to accept_as_valid("person@company.com")
-
#
-
# @yield [Object] actual the actual object (i.e. the value wrapped by `expect`)
-
1
def match_unless_raises(expected_exception=Exception, &match_block)
-
define_user_override(:matches?, match_block) do |actual|
-
@actual = actual
-
begin
-
super(*actual_arg_for(match_block))
-
rescue expected_exception => @rescued_exception
-
false
-
else
-
true
-
end
-
end
-
end
-
-
# Customizes the failure messsage to use when this matcher is
-
# asked to positively match. Only use this when the message
-
# generated by default doesn't suit your needs.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :have_strength do |expected|
-
# match { your_match_logic }
-
#
-
# failure_message do |actual|
-
# "Expected strength of #{expected}, but had #{actual.strength}"
-
# end
-
# end
-
#
-
# @yield [Object] actual the actual object (i.e. the value wrapped by `expect`)
-
1
def failure_message(&definition)
-
define_user_override(__method__, definition)
-
end
-
-
# Customize the failure messsage to use when this matcher is asked
-
# to negatively match. Only use this when the message generated by
-
# default doesn't suit your needs.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :have_strength do |expected|
-
# match { your_match_logic }
-
#
-
# failure_message_when_negated do |actual|
-
# "Expected not to have strength of #{expected}, but did"
-
# end
-
# end
-
#
-
# @yield [Object] actual the actual object (i.e. the value wrapped by `expect`)
-
1
def failure_message_when_negated(&definition)
-
define_user_override(__method__, definition)
-
end
-
-
# Customize the description to use for one-liners. Only use this when
-
# the description generated by default doesn't suit your needs.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :qualify_for do |expected|
-
# match { your_match_logic }
-
#
-
# description do
-
# "qualify for #{expected}"
-
# end
-
# end
-
#
-
# @yield [Object] actual the actual object (i.e. the value wrapped by `expect`)
-
1
def description(&definition)
-
define_user_override(__method__, definition)
-
end
-
-
# Tells the matcher to diff the actual and expected values in the failure
-
# message.
-
1
def diffable
-
define_method(:diffable?) { true }
-
end
-
-
# Declares that the matcher can be used in a block expectation.
-
# Users will not be able to use your matcher in a block
-
# expectation without declaring this.
-
# (e.g. `expect { do_something }.to matcher`).
-
1
def supports_block_expectations
-
define_method(:supports_block_expectations?) { true }
-
end
-
-
# Convenience for defining methods on this matcher to create a fluent
-
# interface. The trick about fluent interfaces is that each method must
-
# return self in order to chain methods together. `chain` handles that
-
# for you. If the method is invoked and the
-
# `include_chain_clauses_in_custom_matcher_descriptions` config option
-
# hash been enabled, the chained method name and args will be added to the
-
# default description and failure message.
-
#
-
# In the common case where you just want the chained method to store some
-
# value(s) for later use (e.g. in `match`), you can provide one or more
-
# attribute names instead of a block; the chained method will store its
-
# arguments in instance variables with those names, and the values will
-
# be exposed via getters.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :have_errors_on do |key|
-
# chain :with do |message|
-
# @message = message
-
# end
-
#
-
# match do |actual|
-
# actual.errors[key] == @message
-
# end
-
# end
-
#
-
# expect(minor).to have_errors_on(:age).with("Not old enough to participate")
-
1
def chain(method_name, *attr_names, &definition)
-
unless block_given? ^ attr_names.any?
-
raise ArgumentError, "You must pass either a block or some attribute names (but not both) to `chain`."
-
end
-
-
definition = assign_attributes(attr_names) if attr_names.any?
-
-
define_user_override(method_name, definition) do |*args, &block|
-
super(*args, &block)
-
@chained_method_clauses.push([method_name, args])
-
self
-
end
-
end
-
-
1
def assign_attributes(attr_names)
-
attr_reader(*attr_names)
-
private(*attr_names)
-
-
lambda do |*attr_values|
-
attr_names.zip(attr_values) do |attr_name, attr_value|
-
instance_variable_set(:"@#{attr_name}", attr_value)
-
end
-
end
-
end
-
-
# assign_attributes isn't defined in the private section below because
-
# that makes MRI 1.9.2 emit a warning about private attributes.
-
1
private :assign_attributes
-
-
1
private
-
-
# Does the following:
-
#
-
# - Defines the named method using a user-provided block
-
# in @user_method_defs, which is included as an ancestor
-
# in the singleton class in which we eval the `define` block.
-
# - Defines an overriden definition for the same method
-
# usign the provided `our_def` block.
-
# - Provides a default `our_def` block for the common case
-
# of needing to call the user's definition with `@actual`
-
# as an arg, but only if their block's arity can handle it.
-
#
-
# This compiles the user block into an actual method, allowing
-
# them to use normal method constructs like `return`
-
# (e.g. for a early guard statement), while allowing us to define
-
# an override that can provide the wrapped handling
-
# (e.g. assigning `@actual`, rescueing errors, etc) and
-
# can `super` to the user's definition.
-
1
def define_user_override(method_name, user_def, &our_def)
-
@user_method_defs.__send__(:define_method, method_name, &user_def)
-
our_def ||= lambda { super(*actual_arg_for(user_def)) }
-
define_method(method_name, &our_def)
-
end
-
-
# Defines deprecated macro methods from RSpec 2 for backwards compatibility.
-
# @deprecated Use the methods from {Macros} instead.
-
1
module Deprecated
-
# @deprecated Use {Macros#match} instead.
-
1
def match_for_should(&definition)
-
RSpec.deprecate("`match_for_should`", :replacement => "`match`")
-
match(&definition)
-
end
-
-
# @deprecated Use {Macros#match_when_negated} instead.
-
1
def match_for_should_not(&definition)
-
RSpec.deprecate("`match_for_should_not`", :replacement => "`match_when_negated`")
-
match_when_negated(&definition)
-
end
-
-
# @deprecated Use {Macros#failure_message} instead.
-
1
def failure_message_for_should(&definition)
-
RSpec.deprecate("`failure_message_for_should`", :replacement => "`failure_message`")
-
failure_message(&definition)
-
end
-
-
# @deprecated Use {Macros#failure_message_when_negated} instead.
-
1
def failure_message_for_should_not(&definition)
-
RSpec.deprecate("`failure_message_for_should_not`", :replacement => "`failure_message_when_negated`")
-
failure_message_when_negated(&definition)
-
end
-
end
-
end
-
-
# Defines default implementations of the matcher
-
# protocol methods for custom matchers. You can
-
# override any of these using the {RSpec::Matchers::DSL::Macros Macros} methods
-
# from within an `RSpec::Matchers.define` block.
-
1
module DefaultImplementations
-
1
include BuiltIn::BaseMatcher::DefaultFailureMessages
-
-
# @api private
-
# Used internally by objects returns by `should` and `should_not`.
-
1
def diffable?
-
false
-
end
-
-
# The default description.
-
1
def description
-
english_name = EnglishPhrasing.split_words(name)
-
expected_list = EnglishPhrasing.list(expected)
-
"#{english_name}#{expected_list}#{chained_method_clause_sentences}"
-
end
-
-
# Matchers do not support block expectations by default. You
-
# must opt-in.
-
1
def supports_block_expectations?
-
false
-
end
-
-
# Most matchers do not expect call stack jumps.
-
1
def expects_call_stack_jump?
-
false
-
end
-
-
1
private
-
-
1
def chained_method_clause_sentences
-
return '' unless Expectations.configuration.include_chain_clauses_in_custom_matcher_descriptions?
-
-
@chained_method_clauses.map do |(method_name, method_args)|
-
english_name = EnglishPhrasing.split_words(method_name)
-
arg_list = EnglishPhrasing.list(method_args)
-
" #{english_name}#{arg_list}"
-
end.join
-
end
-
end
-
-
# The class used for custom matchers. The block passed to
-
# `RSpec::Matchers.define` will be evaluated in the context
-
# of the singleton class of an instance, and will have the
-
# {RSpec::Matchers::DSL::Macros Macros} methods available.
-
1
class Matcher
-
# Provides default implementations for the matcher protocol methods.
-
1
include DefaultImplementations
-
-
# Allows expectation expressions to be used in the match block.
-
1
include RSpec::Matchers
-
-
# Supports the matcher composability features of RSpec 3+.
-
1
include Composable
-
-
# Makes the macro methods available to an `RSpec::Matchers.define` block.
-
1
extend Macros
-
1
extend Macros::Deprecated
-
-
# Exposes the value being matched against -- generally the object
-
# object wrapped by `expect`.
-
1
attr_reader :actual
-
-
# Exposes the exception raised during the matching by `match_unless_raises`.
-
# Could be useful to extract details for a failure message.
-
1
attr_reader :rescued_exception
-
-
# The block parameter used in the expectation
-
1
attr_reader :block_arg
-
-
# The name of the matcher.
-
1
attr_reader :name
-
-
# @api private
-
1
def initialize(name, declarations, matcher_execution_context, *expected, &block_arg)
-
@name = name
-
@actual = nil
-
@expected_as_array = expected
-
@matcher_execution_context = matcher_execution_context
-
@chained_method_clauses = []
-
@block_arg = block_arg
-
-
class << self
-
# See `Macros#define_user_override` above, for an explanation.
-
include(@user_method_defs = Module.new)
-
self
-
end.class_exec(*expected, &declarations)
-
end
-
-
# Provides the expected value. This will return an array if
-
# multiple arguments were passed to the matcher; otherwise it
-
# will return a single value.
-
# @see #expected_as_array
-
1
def expected
-
if expected_as_array.size == 1
-
expected_as_array[0]
-
else
-
expected_as_array
-
end
-
end
-
-
# Returns the expected value as an an array. This exists primarily
-
# to aid in upgrading from RSpec 2.x, since in RSpec 2, `expected`
-
# always returned an array.
-
# @see #expected
-
1
attr_reader :expected_as_array
-
-
# Adds the name (rather than a cryptic hex number)
-
# so we can identify an instance of
-
# the matcher in error messages (e.g. for `NoMethodError`)
-
1
def inspect
-
"#<#{self.class.name} #{name}>"
-
end
-
-
1
if RUBY_VERSION.to_f >= 1.9
-
# Indicates that this matcher responds to messages
-
# from the `@matcher_execution_context` as well.
-
# Also, supports getting a method object for such methods.
-
1
def respond_to_missing?(method, include_private=false)
-
super || @matcher_execution_context.respond_to?(method, include_private)
-
end
-
else # for 1.8.7
-
# :nocov:
-
skipped
# Indicates that this matcher responds to messages
-
skipped
# from the `@matcher_execution_context` as well.
-
skipped
def respond_to?(method, include_private=false)
-
skipped
super || @matcher_execution_context.respond_to?(method, include_private)
-
skipped
end
-
# :nocov:
-
end
-
-
1
private
-
-
1
def actual_arg_for(block)
-
block.arity.zero? ? [] : [@actual]
-
end
-
-
# Takes care of forwarding unhandled messages to the
-
# `@matcher_execution_context` (typically the current
-
# running `RSpec::Core::Example`). This is needed by
-
# rspec-rails so that it can define matchers that wrap
-
# Rails' test helper methods, but it's also a useful
-
# feature in its own right.
-
1
def method_missing(method, *args, &block)
-
if @matcher_execution_context.respond_to?(method)
-
@matcher_execution_context.__send__ method, *args, &block
-
else
-
super(method, *args, &block)
-
end
-
end
-
end
-
end
-
end
-
end
-
-
1
RSpec::Matchers.extend RSpec::Matchers::DSL
-
1
module RSpec
-
1
module Matchers
-
# Facilitates converting ruby objects to English phrases.
-
1
module EnglishPhrasing
-
# Converts a symbol into an English expression.
-
#
-
# split_words(:banana_creme_pie) #=> "banana creme pie"
-
#
-
1
def self.split_words(sym)
-
sym.to_s.gsub(/_/, ' ')
-
end
-
-
# @note The returned string has a leading space except
-
# when given an empty list.
-
#
-
# Converts an object (often a collection of objects)
-
# into an English list.
-
#
-
# list(['banana', 'kiwi', 'mango'])
-
# #=> " \"banana\", \"kiwi\", and \"mango\""
-
#
-
# Given an empty collection, returns the empty string.
-
#
-
# list([]) #=> ""
-
#
-
1
def self.list(obj)
-
return " #{RSpec::Support::ObjectFormatter.format(obj)}" if !obj || Struct === obj
-
items = Array(obj).map { |w| RSpec::Support::ObjectFormatter.format(w) }
-
case items.length
-
when 0
-
""
-
when 1
-
" #{items[0]}"
-
when 2
-
" #{items[0]} and #{items[1]}"
-
else
-
" #{items[0...-1].join(', ')}, and #{items[-1]}"
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
# @api private
-
# Handles list of expected values when there is a need to render
-
# multiple diffs. Also can handle one value.
-
1
class ExpectedsForMultipleDiffs
-
# @private
-
# Default diff label when there is only one matcher in diff
-
# output
-
1
DEFAULT_DIFF_LABEL = "Diff:".freeze
-
-
# @private
-
# Maximum readable matcher description length
-
1
DESCRIPTION_MAX_LENGTH = 65
-
-
1
def initialize(expected_list)
-
@expected_list = expected_list
-
end
-
-
# @api private
-
# Wraps provided expected value in instance of
-
# ExpectedForMultipleDiffs. If provided value is already an
-
# ExpectedForMultipleDiffs then it just returns it.
-
# @param [Any] expected value to be wrapped
-
# @return [RSpec::Matchers::ExpectedsForMultipleDiffs]
-
1
def self.from(expected)
-
return expected if self === expected
-
new([[expected, DEFAULT_DIFF_LABEL]])
-
end
-
-
# @api private
-
# Wraps provided matcher list in instance of
-
# ExpectedForMultipleDiffs.
-
# @param [Array<Any>] matchers list of matchers to wrap
-
# @return [RSpec::Matchers::ExpectedsForMultipleDiffs]
-
1
def self.for_many_matchers(matchers)
-
new(matchers.map { |m| [m.expected, diff_label_for(m)] })
-
end
-
-
# @api private
-
# Returns message with diff(s) appended for provided differ
-
# factory and actual value if there are any
-
# @param [String] message original failure message
-
# @param [Proc] differ
-
# @param [Any] actual value
-
# @return [String]
-
1
def message_with_diff(message, differ, actual)
-
diff = diffs(differ, actual)
-
message = "#{message}\n#{diff}" unless diff.empty?
-
message
-
end
-
-
1
private
-
-
1
def self.diff_label_for(matcher)
-
"Diff for (#{truncated(RSpec::Support::ObjectFormatter.format(matcher))}):"
-
end
-
-
1
def self.truncated(description)
-
return description if description.length <= DESCRIPTION_MAX_LENGTH
-
description[0...DESCRIPTION_MAX_LENGTH - 3] << "..."
-
end
-
-
1
def diffs(differ, actual)
-
@expected_list.map do |(expected, diff_label)|
-
diff = differ.diff(actual, expected)
-
next if diff.strip.empty?
-
"#{diff_label}#{diff}"
-
end.compact.join("\n")
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
class << self
-
# @private
-
1
attr_accessor :last_matcher, :last_expectation_handler
-
end
-
-
# @api private
-
# Used by rspec-core to clear the state used to generate
-
# descriptions after an example.
-
1
def self.clear_generated_description
-
9
self.last_matcher = nil
-
9
self.last_expectation_handler = nil
-
end
-
-
# @api private
-
# Generates an an example description based on the last expectation.
-
# Used by rspec-core's one-liner syntax.
-
1
def self.generated_description
-
return nil if last_expectation_handler.nil?
-
"#{last_expectation_handler.verb} #{last_description}"
-
end
-
-
1
private
-
-
1
def self.last_description
-
last_matcher.respond_to?(:description) ? last_matcher.description : <<-MESSAGE
-
When you call a matcher in an example without a String, like this:
-
-
specify { expect(object).to matcher }
-
-
or this:
-
-
it { is_expected.to matcher }
-
-
RSpec expects the matcher to have a #description method. You should either
-
add a String to the example this matcher is being used in, or give it a
-
description method. Then you won't have to suffer this lengthy warning again.
-
MESSAGE
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
# Provides the necessary plumbing to wrap a matcher with a decorator.
-
# @private
-
1
class MatcherDelegator
-
1
include Composable
-
1
attr_reader :base_matcher
-
-
1
def initialize(base_matcher)
-
@base_matcher = base_matcher
-
end
-
-
1
def method_missing(*args, &block)
-
base_matcher.__send__(*args, &block)
-
end
-
-
1
if ::RUBY_VERSION.to_f > 1.8
-
1
def respond_to_missing?(name, include_all=false)
-
super || base_matcher.respond_to?(name, include_all)
-
end
-
else
-
# :nocov:
-
skipped
def respond_to?(name, include_all=false)
-
skipped
super || base_matcher.respond_to?(name, include_all)
-
skipped
end
-
# :nocov:
-
end
-
-
1
def initialize_copy(other)
-
@base_matcher = @base_matcher.clone
-
super
-
end
-
end
-
end
-
end
-
1
require 'rspec/support'
-
1
RSpec::Support.require_rspec_support 'caller_filter'
-
1
RSpec::Support.require_rspec_support 'warnings'
-
1
RSpec::Support.require_rspec_support 'ruby_features'
-
-
22
RSpec::Support.define_optimized_require_for_rspec(:mocks) { |f| require_relative f }
-
-
%w[
-
instance_method_stasher
-
method_double
-
argument_matchers
-
example_methods
-
proxy
-
test_double
-
argument_list_matcher
-
message_expectation
-
order_group
-
error_generator
-
space
-
mutate_const
-
targets
-
syntax
-
configuration
-
verifying_double
-
version
-
18
].each { |name| RSpec::Support.require_rspec_mocks name }
-
-
# Share the top-level RSpec namespace, because we are a core supported
-
# extension.
-
1
module RSpec
-
# Contains top-level utility methods. While this contains a few
-
# public methods, these are not generally meant to be called from
-
# a test or example. They exist primarily for integration with
-
# test frameworks (such as rspec-core).
-
1
module Mocks
-
# Performs per-test/example setup. This should be called before
-
# an test or example begins.
-
1
def self.setup
-
9
@space_stack << (@space = space.new_scope)
-
end
-
-
# Verifies any message expectations that were set during the
-
# test or example. This should be called at the end of an example.
-
1
def self.verify
-
9
space.verify_all
-
end
-
-
# Cleans up all test double state (including any methods that were
-
# redefined on partial doubles). This _must_ be called after
-
# each example, even if an error was raised during the example.
-
1
def self.teardown
-
9
space.reset_all
-
9
@space_stack.pop
-
9
@space = @space_stack.last || @root_space
-
end
-
-
# Adds an allowance (stub) on `subject`
-
#
-
# @param subject the subject to which the message will be added
-
# @param message a symbol, representing the message that will be
-
# added.
-
# @param opts a hash of options, :expected_from is used to set the
-
# original call site
-
# @yield an optional implementation for the allowance
-
#
-
# @example Defines the implementation of `foo` on `bar`, using the passed block
-
# x = 0
-
# RSpec::Mocks.allow_message(bar, :foo) { x += 1 }
-
1
def self.allow_message(subject, message, opts={}, &block)
-
space.proxy_for(subject).add_stub(message, opts, &block)
-
end
-
-
# Sets a message expectation on `subject`.
-
# @param subject the subject on which the message will be expected
-
# @param message a symbol, representing the message that will be
-
# expected.
-
# @param opts a hash of options, :expected_from is used to set the
-
# original call site
-
# @yield an optional implementation for the expectation
-
#
-
# @example Expect the message `foo` to receive `bar`, then call it
-
# RSpec::Mocks.expect_message(bar, :foo)
-
# bar.foo
-
1
def self.expect_message(subject, message, opts={}, &block)
-
space.proxy_for(subject).add_message_expectation(message, opts, &block)
-
end
-
-
# Call the passed block and verify mocks after it has executed. This allows
-
# mock usage in arbitrary places, such as a `before(:all)` hook.
-
1
def self.with_temporary_scope
-
setup
-
-
begin
-
yield
-
verify
-
ensure
-
teardown
-
end
-
end
-
-
1
class << self
-
# @private
-
1
attr_reader :space
-
end
-
1
@space_stack = []
-
1
@root_space = @space = RSpec::Mocks::RootSpace.new
-
-
# @private
-
1
IGNORED_BACKTRACE_LINE = 'this backtrace line is ignored'
-
-
# To speed up boot time a bit, delay loading optional or rarely
-
# used features until their first use.
-
1
autoload :AnyInstance, "rspec/mocks/any_instance"
-
1
autoload :ExpectChain, "rspec/mocks/message_chain"
-
1
autoload :StubChain, "rspec/mocks/message_chain"
-
1
autoload :MarshalExtension, "rspec/mocks/marshal_extension"
-
-
# Namespace for mock-related matchers.
-
1
module Matchers
-
1
autoload :HaveReceived, "rspec/mocks/matchers/have_received"
-
1
autoload :Receive, "rspec/mocks/matchers/receive"
-
1
autoload :ReceiveMessageChain, "rspec/mocks/matchers/receive_message_chain"
-
1
autoload :ReceiveMessages, "rspec/mocks/matchers/receive_messages"
-
end
-
end
-
end
-
# We intentionally do not use the `RSpec::Support.require...` methods
-
# here so that this file can be loaded individually, as documented
-
# below.
-
1
require 'rspec/mocks/argument_matchers'
-
1
require 'rspec/support/fuzzy_matcher'
-
-
1
module RSpec
-
1
module Mocks
-
# Wrapper for matching arguments against a list of expected values. Used by
-
# the `with` method on a `MessageExpectation`:
-
#
-
# expect(object).to receive(:message).with(:a, 'b', 3)
-
# object.message(:a, 'b', 3)
-
#
-
# Values passed to `with` can be literal values or argument matchers that
-
# match against the real objects .e.g.
-
#
-
# expect(object).to receive(:message).with(hash_including(:a => 'b'))
-
#
-
# Can also be used directly to match the contents of any `Array`. This
-
# enables 3rd party mocking libs to take advantage of rspec's argument
-
# matching without using the rest of rspec-mocks.
-
#
-
# require 'rspec/mocks/argument_list_matcher'
-
# include RSpec::Mocks::ArgumentMatchers
-
#
-
# arg_list_matcher = RSpec::Mocks::ArgumentListMatcher.new(123, hash_including(:a => 'b'))
-
# arg_list_matcher.args_match?(123, :a => 'b')
-
#
-
# This class is immutable.
-
#
-
# @see ArgumentMatchers
-
1
class ArgumentListMatcher
-
# @private
-
1
attr_reader :expected_args
-
-
# @api public
-
# @param [Array] expected_args a list of expected literals and/or argument matchers
-
#
-
# Initializes an `ArgumentListMatcher` with a collection of literal
-
# values and/or argument matchers.
-
#
-
# @see ArgumentMatchers
-
# @see #args_match?
-
1
def initialize(*expected_args)
-
1
@expected_args = expected_args
-
1
ensure_expected_args_valid!
-
end
-
-
# @api public
-
# @param [Array] args
-
#
-
# Matches each element in the `expected_args` against the element in the same
-
# position of the arguments passed to `new`.
-
#
-
# @see #initialize
-
1
def args_match?(*args)
-
Support::FuzzyMatcher.values_match?(resolve_expected_args_based_on(args), args)
-
end
-
-
# @private
-
# Resolves abstract arg placeholders like `no_args` and `any_args` into
-
# a more concrete arg list based on the provided `actual_args`.
-
1
def resolve_expected_args_based_on(actual_args)
-
return [] if [ArgumentMatchers::NoArgsMatcher::INSTANCE] == expected_args
-
-
any_args_index = expected_args.index { |a| ArgumentMatchers::AnyArgsMatcher::INSTANCE == a }
-
return expected_args unless any_args_index
-
-
replace_any_args_with_splat_of_anything(any_args_index, actual_args.count)
-
end
-
-
1
private
-
-
1
def replace_any_args_with_splat_of_anything(before_count, actual_args_count)
-
any_args_count = actual_args_count - expected_args.count + 1
-
after_count = expected_args.count - before_count - 1
-
-
any_args = 1.upto(any_args_count).map { ArgumentMatchers::AnyArgMatcher::INSTANCE }
-
expected_args.first(before_count) + any_args + expected_args.last(after_count)
-
end
-
-
1
def ensure_expected_args_valid!
-
2
if expected_args.count { |a| ArgumentMatchers::AnyArgsMatcher::INSTANCE == a } > 1
-
raise ArgumentError, "`any_args` can only be passed to " \
-
"`with` once but you have passed it multiple times."
-
1
elsif expected_args.count > 1 && expected_args.any? { |a| ArgumentMatchers::NoArgsMatcher::INSTANCE == a }
-
raise ArgumentError, "`no_args` can only be passed as a " \
-
"singleton argument to `with` (i.e. `with(no_args)`), " \
-
"but you have passed additional arguments."
-
end
-
end
-
-
# Value that will match all argument lists.
-
#
-
# @private
-
1
MATCH_ALL = new(ArgumentMatchers::AnyArgsMatcher::INSTANCE)
-
end
-
end
-
end
-
# This cannot take advantage of our relative requires, since this file is a
-
# dependency of `rspec/mocks/argument_list_matcher.rb`. See comment there for
-
# details.
-
1
require 'rspec/support/matcher_definition'
-
-
1
module RSpec
-
1
module Mocks
-
# ArgumentMatchers are placeholders that you can include in message
-
# expectations to match arguments against a broader check than simple
-
# equality.
-
#
-
# With the exception of `any_args` and `no_args`, they all match against
-
# the arg in same position in the argument list.
-
#
-
# @see ArgumentListMatcher
-
1
module ArgumentMatchers
-
# Acts like an arg splat, matching any number of args at any point in an arg list.
-
#
-
# @example
-
# expect(object).to receive(:message).with(1, 2, any_args)
-
#
-
# # matches any of these:
-
# object.message(1, 2)
-
# object.message(1, 2, 3)
-
# object.message(1, 2, 3, 4)
-
1
def any_args
-
AnyArgsMatcher::INSTANCE
-
end
-
-
# Matches any argument at all.
-
#
-
# @example
-
# expect(object).to receive(:message).with(anything)
-
1
def anything
-
AnyArgMatcher::INSTANCE
-
end
-
-
# Matches no arguments.
-
#
-
# @example
-
# expect(object).to receive(:message).with(no_args)
-
1
def no_args
-
NoArgsMatcher::INSTANCE
-
end
-
-
# Matches if the actual argument responds to the specified messages.
-
#
-
# @example
-
# expect(object).to receive(:message).with(duck_type(:hello))
-
# expect(object).to receive(:message).with(duck_type(:hello, :goodbye))
-
1
def duck_type(*args)
-
DuckTypeMatcher.new(*args)
-
end
-
-
# Matches a boolean value.
-
#
-
# @example
-
# expect(object).to receive(:message).with(boolean())
-
1
def boolean
-
BooleanMatcher::INSTANCE
-
end
-
-
# Matches a hash that includes the specified key(s) or key/value pairs.
-
# Ignores any additional keys.
-
#
-
# @example
-
# expect(object).to receive(:message).with(hash_including(:key => val))
-
# expect(object).to receive(:message).with(hash_including(:key))
-
# expect(object).to receive(:message).with(hash_including(:key, :key2 => val2))
-
1
def hash_including(*args)
-
HashIncludingMatcher.new(ArgumentMatchers.anythingize_lonely_keys(*args))
-
end
-
-
# Matches an array that includes the specified items at least once.
-
# Ignores duplicates and additional values
-
#
-
# @example
-
# expect(object).to receive(:message).with(array_including(1,2,3))
-
# expect(object).to receive(:message).with(array_including([1,2,3]))
-
1
def array_including(*args)
-
actually_an_array = Array === args.first && args.count == 1 ? args.first : args
-
ArrayIncludingMatcher.new(actually_an_array)
-
end
-
-
# Matches a hash that doesn't include the specified key(s) or key/value.
-
#
-
# @example
-
# expect(object).to receive(:message).with(hash_excluding(:key => val))
-
# expect(object).to receive(:message).with(hash_excluding(:key))
-
# expect(object).to receive(:message).with(hash_excluding(:key, :key2 => :val2))
-
1
def hash_excluding(*args)
-
HashExcludingMatcher.new(ArgumentMatchers.anythingize_lonely_keys(*args))
-
end
-
-
1
alias_method :hash_not_including, :hash_excluding
-
-
# Matches if `arg.instance_of?(klass)`
-
#
-
# @example
-
# expect(object).to receive(:message).with(instance_of(Thing))
-
1
def instance_of(klass)
-
InstanceOf.new(klass)
-
end
-
-
1
alias_method :an_instance_of, :instance_of
-
-
# Matches if `arg.kind_of?(klass)`
-
#
-
# @example
-
# expect(object).to receive(:message).with(kind_of(Thing))
-
1
def kind_of(klass)
-
KindOf.new(klass)
-
end
-
-
1
alias_method :a_kind_of, :kind_of
-
-
# @private
-
1
def self.anythingize_lonely_keys(*args)
-
hash = args.last.class == Hash ? args.delete_at(-1) : {}
-
args.each { | arg | hash[arg] = AnyArgMatcher::INSTANCE }
-
hash
-
end
-
-
# Intended to be subclassed by stateless, immutable argument matchers.
-
# Provides a `<klass name>::INSTANCE` constant for accessing a global
-
# singleton instance of the matcher. There is no need to construct
-
# multiple instance since there is no state. It also facilities the
-
# special case logic we need for some of these matchers, by making it
-
# easy to do comparisons like: `[klass::INSTANCE] == args` rather than
-
# `args.count == 1 && klass === args.first`.
-
#
-
# @private
-
1
class SingletonMatcher
-
1
private_class_method :new
-
-
1
def self.inherited(subklass)
-
4
subklass.const_set(:INSTANCE, subklass.send(:new))
-
end
-
end
-
-
# @private
-
1
class AnyArgsMatcher < SingletonMatcher
-
1
def description
-
"*(any args)"
-
end
-
end
-
-
# @private
-
1
class AnyArgMatcher < SingletonMatcher
-
1
def ===(_other)
-
true
-
end
-
-
1
def description
-
"anything"
-
end
-
end
-
-
# @private
-
1
class NoArgsMatcher < SingletonMatcher
-
1
def description
-
"no args"
-
end
-
end
-
-
# @private
-
1
class BooleanMatcher < SingletonMatcher
-
1
def ===(value)
-
true == value || false == value
-
end
-
-
1
def description
-
"boolean"
-
end
-
end
-
-
# @private
-
1
class BaseHashMatcher
-
1
def initialize(expected)
-
@expected = expected
-
end
-
-
1
def ===(predicate, actual)
-
@expected.__send__(predicate) do |k, v|
-
actual.key?(k) && Support::FuzzyMatcher.values_match?(v, actual[k])
-
end
-
rescue NoMethodError
-
false
-
end
-
-
1
def description(name)
-
"#{name}(#{formatted_expected_hash.inspect.sub(/^\{/, "").sub(/\}$/, "")})"
-
end
-
-
1
private
-
-
1
def formatted_expected_hash
-
Hash[
-
@expected.map do |k, v|
-
k = RSpec::Support.rspec_description_for_object(k)
-
v = RSpec::Support.rspec_description_for_object(v)
-
-
[k, v]
-
end
-
]
-
end
-
end
-
-
# @private
-
1
class HashIncludingMatcher < BaseHashMatcher
-
1
def ===(actual)
-
super(:all?, actual)
-
end
-
-
1
def description
-
super("hash_including")
-
end
-
end
-
-
# @private
-
1
class HashExcludingMatcher < BaseHashMatcher
-
1
def ===(actual)
-
super(:none?, actual)
-
end
-
-
1
def description
-
super("hash_not_including")
-
end
-
end
-
-
# @private
-
1
class ArrayIncludingMatcher
-
1
def initialize(expected)
-
@expected = expected
-
end
-
-
1
def ===(actual)
-
actual = actual.uniq
-
@expected.uniq.all? do |expected_element|
-
actual.any? do |actual_element|
-
RSpec::Support::FuzzyMatcher.values_match?(expected_element, actual_element)
-
end
-
end
-
end
-
-
1
def description
-
"array_including(#{formatted_expected_values})"
-
end
-
-
1
private
-
-
1
def formatted_expected_values
-
@expected.map do |x|
-
RSpec::Support.rspec_description_for_object(x)
-
end.join(", ")
-
end
-
end
-
-
# @private
-
1
class DuckTypeMatcher
-
1
def initialize(*methods_to_respond_to)
-
@methods_to_respond_to = methods_to_respond_to
-
end
-
-
1
def ===(value)
-
@methods_to_respond_to.all? { |message| value.respond_to?(message) }
-
end
-
-
1
def description
-
"duck_type(#{@methods_to_respond_to.map(&:inspect).join(', ')})"
-
end
-
end
-
-
# @private
-
1
class InstanceOf
-
1
def initialize(klass)
-
@klass = klass
-
end
-
-
1
def ===(actual)
-
actual.instance_of?(@klass)
-
end
-
-
1
def description
-
"an_instance_of(#{@klass.name})"
-
end
-
end
-
-
# @private
-
1
class KindOf
-
1
def initialize(klass)
-
@klass = klass
-
end
-
-
1
def ===(actual)
-
actual.kind_of?(@klass)
-
end
-
-
1
def description
-
"kind of #{@klass.name}"
-
end
-
end
-
-
1
matcher_namespace = name + '::'
-
1
::RSpec::Support.register_matcher_definition do |object|
-
# This is the best we have for now. We should tag all of our matchers
-
# with a module or something so we can test for it directly.
-
#
-
# (Note Module#parent in ActiveSupport is defined in a similar way.)
-
begin
-
object.class.name.include?(matcher_namespace)
-
rescue NoMethodError
-
# Some objects, like BasicObject, don't implemented standard
-
# reflection methods.
-
false
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# Provides configuration options for rspec-mocks.
-
1
class Configuration
-
1
def initialize
-
1
@allow_message_expectations_on_nil = nil
-
1
@yield_receiver_to_any_instance_implementation_blocks = true
-
1
@verify_doubled_constant_names = false
-
1
@transfer_nested_constants = false
-
1
@verify_partial_doubles = false
-
end
-
-
# Sets whether RSpec will warn, ignore, or fail a test when
-
# expectations are set on nil.
-
# By default, when this flag is not set, warning messages are issued when
-
# expectations are set on nil. This is to prevent false-positives and to
-
# catch potential bugs early on.
-
# When set to `true`, warning messages are suppressed.
-
# When set to `false`, it will raise an error.
-
#
-
# @example
-
# RSpec.configure do |config|
-
# config.mock_with :rspec do |mocks|
-
# mocks.allow_message_expectations_on_nil = false
-
# end
-
# end
-
1
attr_accessor :allow_message_expectations_on_nil
-
-
1
def yield_receiver_to_any_instance_implementation_blocks?
-
@yield_receiver_to_any_instance_implementation_blocks
-
end
-
-
# Sets whether or not RSpec will yield the receiving instance of a
-
# message to blocks that are used for any_instance stub implementations.
-
# When set, the first yielded argument will be the receiving instance.
-
# Defaults to `true`.
-
#
-
# @example
-
# RSpec.configure do |rspec|
-
# rspec.mock_with :rspec do |mocks|
-
# mocks.yield_receiver_to_any_instance_implementation_blocks = false
-
# end
-
# end
-
1
attr_writer :yield_receiver_to_any_instance_implementation_blocks
-
-
# Adds `stub` and `should_receive` to the given
-
# modules or classes. This is usually only necessary
-
# if you application uses some proxy classes that
-
# "strip themselves down" to a bare minimum set of
-
# methods and remove `stub` and `should_receive` in
-
# the process.
-
#
-
# @example
-
# RSpec.configure do |rspec|
-
# rspec.mock_with :rspec do |mocks|
-
# mocks.add_stub_and_should_receive_to Delegator
-
# end
-
# end
-
#
-
1
def add_stub_and_should_receive_to(*modules)
-
modules.each do |mod|
-
Syntax.enable_should(mod)
-
end
-
end
-
-
# Provides the ability to set either `expect`,
-
# `should` or both syntaxes. RSpec uses `expect`
-
# syntax by default. This is needed if you want to
-
# explicitly enable `should` syntax and/or explicitly
-
# disable `expect` syntax.
-
#
-
# @example
-
# RSpec.configure do |rspec|
-
# rspec.mock_with :rspec do |mocks|
-
# mocks.syntax = [:expect, :should]
-
# end
-
# end
-
#
-
1
def syntax=(*values)
-
1
syntaxes = values.flatten
-
1
if syntaxes.include?(:expect)
-
1
Syntax.enable_expect
-
else
-
Syntax.disable_expect
-
end
-
-
1
if syntaxes.include?(:should)
-
1
Syntax.enable_should
-
else
-
Syntax.disable_should
-
end
-
end
-
-
# Returns an array with a list of syntaxes
-
# that are enabled.
-
#
-
# @example
-
# unless RSpec::Mocks.configuration.syntax.include?(:expect)
-
# raise "this RSpec extension gem requires the rspec-mocks `:expect` syntax"
-
# end
-
#
-
1
def syntax
-
syntaxes = []
-
syntaxes << :should if Syntax.should_enabled?
-
syntaxes << :expect if Syntax.expect_enabled?
-
syntaxes
-
end
-
-
1
def verify_doubled_constant_names?
-
!!@verify_doubled_constant_names
-
end
-
-
# When this is set to true, an error will be raised when
-
# `instance_double` or `class_double` is given the name of an undefined
-
# constant. You probably only want to set this when running your entire
-
# test suite, with all production code loaded. Setting this for an
-
# isolated unit test will prevent you from being able to isolate it!
-
1
attr_writer :verify_doubled_constant_names
-
-
# Provides a way to perform customisations when verifying doubles.
-
#
-
# @example
-
# RSpec::Mocks.configuration.before_verifying_doubles do |ref|
-
# ref.some_method!
-
# end
-
1
def before_verifying_doubles(&block)
-
verifying_double_callbacks << block
-
end
-
1
alias :when_declaring_verifying_double :before_verifying_doubles
-
-
# @api private
-
# Returns an array of blocks to call when verifying doubles
-
1
def verifying_double_callbacks
-
@verifying_double_callbacks ||= []
-
end
-
-
1
def transfer_nested_constants?
-
!!@transfer_nested_constants
-
end
-
-
# Sets the default for the `transfer_nested_constants` option when
-
# stubbing constants.
-
1
attr_writer :transfer_nested_constants
-
-
# When set to true, partial mocks will be verified the same as object
-
# doubles. Any stubs will have their arguments checked against the original
-
# method, and methods that do not exist cannot be stubbed.
-
1
def verify_partial_doubles=(val)
-
@verify_partial_doubles = !!val
-
end
-
-
1
def verify_partial_doubles?
-
@verify_partial_doubles
-
end
-
-
1
if ::RSpec.respond_to?(:configuration)
-
1
def color?
-
::RSpec.configuration.color_enabled?
-
end
-
else
-
# Indicates whether or not diffs should be colored.
-
# Delegates to rspec-core's color option if rspec-core
-
# is loaded; otherwise you can set it here.
-
attr_writer :color
-
-
# Indicates whether or not diffs should be colored.
-
# Delegates to rspec-core's color option if rspec-core
-
# is loaded; otherwise you can set it here.
-
def color?
-
@color
-
end
-
end
-
-
# Monkey-patch `Marshal.dump` to enable dumping of mocked or stubbed
-
# objects. By default this will not work since RSpec mocks works by
-
# adding singleton methods that cannot be serialized. This patch removes
-
# these singleton methods before serialization. Setting to falsey removes
-
# the patch.
-
#
-
# This method is idempotent.
-
1
def patch_marshal_to_support_partial_doubles=(val)
-
if val
-
RSpec::Mocks::MarshalExtension.patch!
-
else
-
RSpec::Mocks::MarshalExtension.unpatch!
-
end
-
end
-
-
# @api private
-
# Resets the configured syntax to the default.
-
1
def reset_syntaxes_to_default
-
1
self.syntax = [:should, :expect]
-
1
RSpec::Mocks::Syntax.warn_about_should!
-
end
-
end
-
-
# Mocks specific configuration, as distinct from `RSpec.configuration`
-
# which is core RSpec configuration.
-
1
def self.configuration
-
1
@configuration ||= Configuration.new
-
end
-
-
1
configuration.reset_syntaxes_to_default
-
end
-
end
-
1
RSpec::Support.require_rspec_support "object_formatter"
-
-
1
module RSpec
-
1
module Mocks
-
# Raised when a message expectation is not satisfied.
-
1
MockExpectationError = Class.new(Exception)
-
-
# Raised when a test double is used after it has been torn
-
# down (typically at the end of an rspec-core example).
-
1
ExpiredTestDoubleError = Class.new(MockExpectationError)
-
-
# Raised when doubles or partial doubles are used outside of the per-test lifecycle.
-
1
OutsideOfExampleError = Class.new(StandardError)
-
-
# Raised when an expectation customization method (e.g. `with`,
-
# `and_return`) is called on a message expectation which has already been
-
# invoked.
-
1
MockExpectationAlreadyInvokedError = Class.new(Exception)
-
-
# Raised for situations that RSpec cannot support due to mutations made
-
# externally on arguments that RSpec is holding onto to use for later
-
# comparisons.
-
#
-
# @deprecated We no longer raise this error but the constant remains until
-
# RSpec 4 for SemVer reasons.
-
1
CannotSupportArgMutationsError = Class.new(StandardError)
-
-
# @private
-
1
UnsupportedMatcherError = Class.new(StandardError)
-
# @private
-
1
NegationUnsupportedError = Class.new(StandardError)
-
# @private
-
1
VerifyingDoubleNotDefinedError = Class.new(StandardError)
-
-
# @private
-
1
class ErrorGenerator
-
1
attr_writer :opts
-
-
1
def initialize(target=nil)
-
@target = target
-
end
-
-
# @private
-
1
def opts
-
@opts ||= {}
-
end
-
-
# @private
-
1
def raise_unexpected_message_error(message, args)
-
__raise "#{intro} received unexpected message :#{message} with #{format_args(args)}"
-
end
-
-
# @private
-
1
def raise_unexpected_message_args_error(expectation, args_for_multiple_calls, source_id=nil)
-
__raise error_message(expectation, args_for_multiple_calls), nil, source_id
-
end
-
-
# @private
-
1
def raise_missing_default_stub_error(expectation, args_for_multiple_calls)
-
message = error_message(expectation, args_for_multiple_calls)
-
message << "\n Please stub a default value first if message might be received with other args as well. \n"
-
-
__raise message
-
end
-
-
# @private
-
1
def raise_similar_message_args_error(expectation, args_for_multiple_calls, backtrace_line=nil)
-
__raise error_message(expectation, args_for_multiple_calls), backtrace_line
-
end
-
-
1
def default_error_message(expectation, expected_args, actual_args)
-
"#{intro} received #{expectation.message.inspect} #{unexpected_arguments_message(expected_args, actual_args)}"
-
end
-
-
# rubocop:disable Style/ParameterLists
-
# @private
-
1
def raise_expectation_error(message, expected_received_count, argument_list_matcher,
-
actual_received_count, expectation_count_type, args,
-
backtrace_line=nil, source_id=nil)
-
expected_part = expected_part_of_expectation_error(expected_received_count, expectation_count_type, argument_list_matcher)
-
received_part = received_part_of_expectation_error(actual_received_count, args)
-
__raise "(#{intro(:unwrapped)}).#{message}#{format_args(args)}\n #{expected_part}\n #{received_part}", backtrace_line, source_id
-
end
-
# rubocop:enable Style/ParameterLists
-
-
# @private
-
1
def raise_unimplemented_error(doubled_module, method_name, object)
-
message = case object
-
when InstanceVerifyingDouble
-
"the %s class does not implement the instance method: %s" <<
-
if ObjectMethodReference.for(doubled_module, method_name).implemented?
-
". Perhaps you meant to use `class_double` instead?"
-
else
-
""
-
end
-
when ClassVerifyingDouble
-
"the %s class does not implement the class method: %s" <<
-
if InstanceMethodReference.for(doubled_module, method_name).implemented?
-
". Perhaps you meant to use `instance_double` instead?"
-
else
-
""
-
end
-
else
-
"%s does not implement: %s"
-
end
-
-
__raise message % [doubled_module.description, method_name]
-
end
-
-
# @private
-
1
def raise_non_public_error(method_name, visibility)
-
raise NoMethodError, "%s method `%s' called on %s" % [
-
visibility, method_name, intro
-
]
-
end
-
-
# @private
-
1
def raise_invalid_arguments_error(verifier)
-
__raise verifier.error_message
-
end
-
-
# @private
-
1
def raise_expired_test_double_error
-
raise ExpiredTestDoubleError,
-
"#{intro} was originally created in one example but has leaked into " \
-
"another example and can no longer be used. rspec-mocks' doubles are " \
-
"designed to only last for one example, and you need to create a new " \
-
"one in each example you wish to use it for."
-
end
-
-
# @private
-
1
def describe_expectation(verb, message, expected_received_count, _actual_received_count, args)
-
"#{verb} #{message}#{format_args(args)} #{count_message(expected_received_count)}"
-
end
-
-
# @private
-
1
def raise_out_of_order_error(message)
-
__raise "#{intro} received :#{message} out of order"
-
end
-
-
# @private
-
1
def raise_missing_block_error(args_to_yield)
-
__raise "#{intro} asked to yield |#{arg_list(args_to_yield)}| but no block was passed"
-
end
-
-
# @private
-
1
def raise_wrong_arity_error(args_to_yield, signature)
-
__raise "#{intro} yielded |#{arg_list(args_to_yield)}| to block with #{signature.description}"
-
end
-
-
# @private
-
1
def raise_only_valid_on_a_partial_double(method)
-
__raise "#{intro} is a pure test double. `#{method}` is only " \
-
"available on a partial double."
-
end
-
-
# @private
-
1
def raise_expectation_on_unstubbed_method(method)
-
__raise "#{intro} expected to have received #{method}, but that " \
-
"object is not a spy or method has not been stubbed."
-
end
-
-
# @private
-
1
def raise_expectation_on_mocked_method(method)
-
__raise "#{intro} expected to have received #{method}, but that " \
-
"method has been mocked instead of stubbed or spied."
-
end
-
-
# @private
-
1
def raise_double_negation_error(wrapped_expression)
-
__raise "Isn't life confusing enough? You've already set a " \
-
"negative message expectation and now you are trying to " \
-
"negate it again with `never`. What does an expression like " \
-
"`#{wrapped_expression}.not_to receive(:msg).never` even mean?"
-
end
-
-
# @private
-
1
def raise_verifying_double_not_defined_error(ref)
-
notify(VerifyingDoubleNotDefinedError.new(
-
"#{ref.description.inspect} is not a defined constant. " \
-
"Perhaps you misspelt it? " \
-
"Disable check with `verify_doubled_constant_names` configuration option."
-
))
-
end
-
-
# @private
-
1
def raise_have_received_disallowed(type, reason)
-
__raise "Using #{type}(...) with the `have_received` " \
-
"matcher is not supported#{reason}."
-
end
-
-
# @private
-
1
def raise_cant_constrain_count_for_negated_have_received_error(count_constraint)
-
__raise "can't use #{count_constraint} when negative"
-
end
-
-
# @private
-
1
def raise_method_not_stubbed_error(method_name)
-
__raise "The method `#{method_name}` was not stubbed or was already unstubbed"
-
end
-
-
# @private
-
1
def raise_already_invoked_error(message, calling_customization)
-
error_message = "The message expectation for #{intro}.#{message} has already been invoked " \
-
"and cannot be modified further (e.g. using `#{calling_customization}`). All message expectation " \
-
"customizations must be applied before it is used for the first time."
-
-
notify MockExpectationAlreadyInvokedError.new(error_message)
-
end
-
-
1
def raise_expectation_on_nil_error(method_name)
-
__raise expectation_on_nil_message(method_name)
-
end
-
-
1
def expectation_on_nil_message(method_name)
-
"An expectation of `:#{method_name}` was set on `nil`. " \
-
"To allow expectations on `nil` and suppress this message, set `config.allow_expectations_on_nil` to `true`. " \
-
"To disallow expectations on `nil`, set `config.allow_expectations_on_nil` to `false`"
-
end
-
-
1
private
-
-
1
def received_part_of_expectation_error(actual_received_count, args)
-
"received: #{count_message(actual_received_count)}" +
-
method_call_args_description(args) do
-
actual_received_count > 0 && args.length > 0
-
end
-
end
-
-
1
def expected_part_of_expectation_error(expected_received_count, expectation_count_type, argument_list_matcher)
-
"expected: #{count_message(expected_received_count, expectation_count_type)}" +
-
method_call_args_description(argument_list_matcher.expected_args) do
-
argument_list_matcher.expected_args.length > 0
-
end
-
end
-
-
1
def method_call_args_description(args)
-
case args.first
-
when ArgumentMatchers::AnyArgsMatcher then " with any arguments"
-
when ArgumentMatchers::NoArgsMatcher then " with no arguments"
-
else
-
if yield
-
" with arguments: #{format_args(args)}"
-
else
-
""
-
end
-
end
-
end
-
-
1
def unexpected_arguments_message(expected_args_string, actual_args_string)
-
"with unexpected arguments\n expected: #{expected_args_string}\n got: #{actual_args_string}"
-
end
-
-
1
def error_message(expectation, args_for_multiple_calls)
-
expected_args = format_args(expectation.expected_args)
-
actual_args = format_received_args(args_for_multiple_calls)
-
message = default_error_message(expectation, expected_args, actual_args)
-
-
if args_for_multiple_calls.one?
-
diff = diff_message(expectation.expected_args, args_for_multiple_calls.first)
-
message << "\nDiff:#{diff}" unless diff.strip.empty?
-
end
-
-
message
-
end
-
-
1
def diff_message(expected_args, actual_args)
-
formatted_expected_args = expected_args.map do |x|
-
RSpec::Support.rspec_description_for_object(x)
-
end
-
-
formatted_expected_args, actual_args = unpack_string_args(formatted_expected_args, actual_args)
-
-
differ.diff(actual_args, formatted_expected_args)
-
end
-
-
1
def unpack_string_args(formatted_expected_args, actual_args)
-
if [formatted_expected_args, actual_args].all? { |x| list_of_exactly_one_string?(x) }
-
[formatted_expected_args.first, actual_args.first]
-
else
-
[formatted_expected_args, actual_args]
-
end
-
end
-
-
1
def list_of_exactly_one_string?(args)
-
Array === args && args.count == 1 && String === args.first
-
end
-
-
1
def differ
-
RSpec::Support::Differ.new(:color => RSpec::Mocks.configuration.color?)
-
end
-
-
1
def intro(unwrapped=false)
-
case @target
-
when TestDouble then TestDoubleFormatter.format(@target, unwrapped)
-
when Class then
-
formatted = "#{@target.inspect} (class)"
-
return formatted if unwrapped
-
"#<#{formatted}>"
-
when NilClass then "nil"
-
else @target
-
end
-
end
-
-
1
def __raise(message, backtrace_line=nil, source_id=nil)
-
message = opts[:message] unless opts[:message].nil?
-
exception = RSpec::Mocks::MockExpectationError.new(message)
-
prepend_to_backtrace(exception, backtrace_line) if backtrace_line
-
notify exception, :source_id => source_id
-
end
-
-
1
if RSpec::Support::Ruby.jruby?
-
def prepend_to_backtrace(exception, line)
-
raise exception
-
rescue RSpec::Mocks::MockExpectationError => with_backtrace
-
with_backtrace.backtrace.unshift(line)
-
end
-
else
-
1
def prepend_to_backtrace(exception, line)
-
exception.set_backtrace(caller.unshift line)
-
end
-
end
-
-
1
def notify(*args)
-
RSpec::Support.notify_failure(*args)
-
end
-
-
1
def format_args(args)
-
return "(no args)" if args.empty?
-
"(#{arg_list(args)})"
-
end
-
-
1
def arg_list(args)
-
args.map { |arg| RSpec::Support::ObjectFormatter.format(arg) }.join(", ")
-
end
-
-
1
def format_received_args(args_for_multiple_calls)
-
grouped_args(args_for_multiple_calls).map do |args_for_one_call, index|
-
"#{format_args(args_for_one_call)}#{group_count(index, args_for_multiple_calls)}"
-
end.join("\n ")
-
end
-
-
1
def count_message(count, expectation_count_type=nil)
-
return "at least #{times(count.abs)}" if count < 0 || expectation_count_type == :at_least
-
return "at most #{times(count)}" if expectation_count_type == :at_most
-
times(count)
-
end
-
-
1
def times(count)
-
"#{count} time#{count == 1 ? '' : 's'}"
-
end
-
-
1
def grouped_args(args)
-
Hash[args.group_by { |x| x }.map { |k, v| [k, v.count] }]
-
end
-
-
1
def group_count(index, args)
-
" (#{times(index)})" if args.size > 1 || index > 1
-
end
-
end
-
-
# @private
-
1
def self.error_generator
-
@error_generator ||= ErrorGenerator.new
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_mocks 'object_reference'
-
-
1
module RSpec
-
1
module Mocks
-
# Contains methods intended to be used from within code examples.
-
# Mix this in to your test context (such as a test framework base class)
-
# to use rspec-mocks with your test framework. If you're using rspec-core,
-
# it'll take care of doing this for you.
-
1
module ExampleMethods
-
1
include RSpec::Mocks::ArgumentMatchers
-
-
# @overload double()
-
# @overload double(name)
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload double(stubs)
-
# @param stubs (Hash) hash of message/return-value pairs
-
# @overload double(name, stubs)
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs (Hash) hash of message/return-value pairs
-
# @return (Double)
-
#
-
# Constructs an instance of [RSpec::Mocks::Double](RSpec::Mocks::Double) configured
-
# with an optional name, used for reporting in failure messages, and an optional
-
# hash of message/return-value pairs.
-
#
-
# @example
-
# book = double("book", :title => "The RSpec Book")
-
# book.title #=> "The RSpec Book"
-
#
-
# card = double("card", :suit => "Spades", :rank => "A")
-
# card.suit #=> "Spades"
-
# card.rank #=> "A"
-
#
-
1
def double(*args)
-
ExampleMethods.declare_double(Double, *args)
-
end
-
-
# @overload instance_double(doubled_class)
-
# @param doubled_class [String, Class]
-
# @overload instance_double(doubled_class, name)
-
# @param doubled_class [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload instance_double(doubled_class, stubs)
-
# @param doubled_class [String, Class]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @overload instance_double(doubled_class, name, stubs)
-
# @param doubled_class [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return InstanceVerifyingDouble
-
#
-
# Constructs a test double against a specific class. If the given class
-
# name has been loaded, only instance methods defined on the class are
-
# allowed to be stubbed. In all other ways it behaves like a
-
# [double](double).
-
1
def instance_double(doubled_class, *args)
-
ref = ObjectReference.for(doubled_class)
-
ExampleMethods.declare_verifying_double(InstanceVerifyingDouble, ref, *args)
-
end
-
-
# @overload class_double(doubled_class)
-
# @param doubled_class [String, Module]
-
# @overload class_double(doubled_class, name)
-
# @param doubled_class [String, Module]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload class_double(doubled_class, stubs)
-
# @param doubled_class [String, Module]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @overload class_double(doubled_class, name, stubs)
-
# @param doubled_class [String, Module]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return ClassVerifyingDouble
-
#
-
# Constructs a test double against a specific class. If the given class
-
# name has been loaded, only class methods defined on the class are
-
# allowed to be stubbed. In all other ways it behaves like a
-
# [double](double).
-
1
def class_double(doubled_class, *args)
-
ref = ObjectReference.for(doubled_class)
-
ExampleMethods.declare_verifying_double(ClassVerifyingDouble, ref, *args)
-
end
-
-
# @overload object_double(object_or_name)
-
# @param object_or_name [String, Object]
-
# @overload object_double(object_or_name, name)
-
# @param object_or_name [String, Object]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload object_double(object_or_name, stubs)
-
# @param object_or_name [String, Object]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @overload object_double(object_or_name, name, stubs)
-
# @param object_or_name [String, Object]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return ObjectVerifyingDouble
-
#
-
# Constructs a test double against a specific object. Only the methods
-
# the object responds to are allowed to be stubbed. If a String argument
-
# is provided, it is assumed to reference a constant object which is used
-
# for verification. In all other ways it behaves like a [double](double).
-
1
def object_double(object_or_name, *args)
-
ref = ObjectReference.for(object_or_name, :allow_direct_object_refs)
-
ExampleMethods.declare_verifying_double(ObjectVerifyingDouble, ref, *args)
-
end
-
-
# @overload spy()
-
# @overload spy(name)
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload spy(stubs)
-
# @param stubs (Hash) hash of message/return-value pairs
-
# @overload spy(name, stubs)
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs (Hash) hash of message/return-value pairs
-
# @return (Double)
-
#
-
# Constructs a test double that is optimized for use with
-
# `have_received`. With a normal double one has to stub methods in order
-
# to be able to spy them. A spy automatically spies on all methods.
-
1
def spy(*args)
-
double(*args).as_null_object
-
end
-
-
# @overload instance_spy(doubled_class)
-
# @param doubled_class [String, Class]
-
# @overload instance_spy(doubled_class, name)
-
# @param doubled_class [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload instance_spy(doubled_class, stubs)
-
# @param doubled_class [String, Class]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @overload instance_spy(doubled_class, name, stubs)
-
# @param doubled_class [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return InstanceVerifyingDouble
-
#
-
# Constructs a test double that is optimized for use with `have_received`
-
# against a specific class. If the given class name has been loaded, only
-
# instance methods defined on the class are allowed to be stubbed. With
-
# a normal double one has to stub methods in order to be able to spy
-
# them. An instance_spy automatically spies on all instance methods to
-
# which the class responds.
-
1
def instance_spy(*args)
-
instance_double(*args).as_null_object
-
end
-
-
# @overload object_spy(object_or_name)
-
# @param object_or_name [String, Object]
-
# @overload object_spy(object_or_name, name)
-
# @param object_or_name [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload object_spy(object_or_name, stubs)
-
# @param object_or_name [String, Object]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @overload object_spy(object_or_name, name, stubs)
-
# @param object_or_name [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return ObjectVerifyingDouble
-
#
-
# Constructs a test double that is optimized for use with `have_received`
-
# against a specific object. Only instance methods defined on the object
-
# are allowed to be stubbed. With a normal double one has to stub
-
# methods in order to be able to spy them. An object_spy automatically
-
# spies on all methods to which the object responds.
-
1
def object_spy(*args)
-
object_double(*args).as_null_object
-
end
-
-
# @overload class_spy(doubled_class)
-
# @param doubled_class [String, Module]
-
# @overload class_spy(doubled_class, name)
-
# @param doubled_class [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload class_spy(doubled_class, stubs)
-
# @param doubled_class [String, Module]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @overload class_spy(doubled_class, name, stubs)
-
# @param doubled_class [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return ClassVerifyingDouble
-
#
-
# Constructs a test double that is optimized for use with `have_received`
-
# against a specific class. If the given class name has been loaded,
-
# only class methods defined on the class are allowed to be stubbed.
-
# With a normal double one has to stub methods in order to be able to spy
-
# them. An class_spy automatically spies on all class methods to which the
-
# class responds.
-
1
def class_spy(*args)
-
class_double(*args).as_null_object
-
end
-
-
# Disables warning messages about expectations being set on nil.
-
#
-
# By default warning messages are issued when expectations are set on
-
# nil. This is to prevent false-positives and to catch potential bugs
-
# early on.
-
# @deprecated Use {RSpec::Mocks::Configuration#allow_message_expectations_on_nil} instead.
-
1
def allow_message_expectations_on_nil
-
RSpec::Mocks.space.proxy_for(nil).warn_about_expectations = false
-
end
-
-
# Stubs the named constant with the given value.
-
# Like method stubs, the constant will be restored
-
# to its original value (or lack of one, if it was
-
# undefined) when the example completes.
-
#
-
# @param constant_name [String] The fully qualified name of the constant. The current
-
# constant scoping at the point of call is not considered.
-
# @param value [Object] The value to make the constant refer to. When the
-
# example completes, the constant will be restored to its prior state.
-
# @param options [Hash] Stubbing options.
-
# @option options :transfer_nested_constants [Boolean, Array<Symbol>] Determines
-
# what nested constants, if any, will be transferred from the original value
-
# of the constant to the new value of the constant. This only works if both
-
# the original and new values are modules (or classes).
-
# @return [Object] the stubbed value of the constant
-
#
-
# @example
-
# stub_const("MyClass", Class.new) # => Replaces (or defines) MyClass with a new class object.
-
# stub_const("SomeModel::PER_PAGE", 5) # => Sets SomeModel::PER_PAGE to 5.
-
#
-
# class CardDeck
-
# SUITS = [:Spades, :Diamonds, :Clubs, :Hearts]
-
# NUM_CARDS = 52
-
# end
-
#
-
# stub_const("CardDeck", Class.new)
-
# CardDeck::SUITS # => uninitialized constant error
-
# CardDeck::NUM_CARDS # => uninitialized constant error
-
#
-
# stub_const("CardDeck", Class.new, :transfer_nested_constants => true)
-
# CardDeck::SUITS # => our suits array
-
# CardDeck::NUM_CARDS # => 52
-
#
-
# stub_const("CardDeck", Class.new, :transfer_nested_constants => [:SUITS])
-
# CardDeck::SUITS # => our suits array
-
# CardDeck::NUM_CARDS # => uninitialized constant error
-
1
def stub_const(constant_name, value, options={})
-
ConstantMutator.stub(constant_name, value, options)
-
end
-
-
# Hides the named constant with the given value. The constant will be
-
# undefined for the duration of the test.
-
#
-
# Like method stubs, the constant will be restored to its original value
-
# when the example completes.
-
#
-
# @param constant_name [String] The fully qualified name of the constant.
-
# The current constant scoping at the point of call is not considered.
-
#
-
# @example
-
# hide_const("MyClass") # => MyClass is now an undefined constant
-
1
def hide_const(constant_name)
-
ConstantMutator.hide(constant_name)
-
end
-
-
# Verifies that the given object received the expected message during the
-
# course of the test. On a spy objects or as null object doubles this
-
# works for any method, on other objects the method must have
-
# been stubbed beforehand in order for messages to be verified.
-
#
-
# Stubbing and verifying messages received in this way implements the
-
# Test Spy pattern.
-
#
-
# @param method_name [Symbol] name of the method expected to have been
-
# called.
-
#
-
# @example
-
# invitation = double('invitation', accept: true)
-
# user.accept_invitation(invitation)
-
# expect(invitation).to have_received(:accept)
-
#
-
# # You can also use most message expectations:
-
# expect(invitation).to have_received(:accept).with(mailer).once
-
#
-
# @note `have_received(...).with(...)` is unable to work properly when
-
# passed arguments are mutated after the spy records the received message.
-
1
def have_received(method_name, &block)
-
Matchers::HaveReceived.new(method_name, &block)
-
end
-
-
# @method expect
-
# Used to wrap an object in preparation for setting a mock expectation
-
# on it.
-
#
-
# @example
-
# expect(obj).to receive(:foo).with(5).and_return(:return_value)
-
#
-
# @note This method is usually provided by rspec-expectations. However,
-
# if you use rspec-mocks without rspec-expectations, there's a definition
-
# of it that is made available here. If you disable the `:expect` syntax
-
# this method will be undefined.
-
-
# @method allow
-
# Used to wrap an object in preparation for stubbing a method
-
# on it.
-
#
-
# @example
-
# allow(dbl).to receive(:foo).with(5).and_return(:return_value)
-
#
-
# @note If you disable the `:expect` syntax this method will be undefined.
-
-
# @method expect_any_instance_of
-
# Used to wrap a class in preparation for setting a mock expectation
-
# on instances of it.
-
#
-
# @example
-
# expect_any_instance_of(MyClass).to receive(:foo)
-
#
-
# @note If you disable the `:expect` syntax this method will be undefined.
-
-
# @method allow_any_instance_of
-
# Used to wrap a class in preparation for stubbing a method
-
# on instances of it.
-
#
-
# @example
-
# allow_any_instance_of(MyClass).to receive(:foo)
-
#
-
# @note This is only available when you have enabled the `expect` syntax.
-
-
# @method receive
-
# Used to specify a message that you expect or allow an object
-
# to receive. The object returned by `receive` supports the same
-
# fluent interface that `should_receive` and `stub` have always
-
# supported, allowing you to constrain the arguments or number of
-
# times, and configure how the object should respond to the message.
-
#
-
# @example
-
# expect(obj).to receive(:hello).with("world").exactly(3).times
-
#
-
# @note If you disable the `:expect` syntax this method will be undefined.
-
-
# @method receive_messages
-
# Shorthand syntax used to setup message(s), and their return value(s),
-
# that you expect or allow an object to receive. The method takes a hash
-
# of messages and their respective return values. Unlike with `receive`,
-
# you cannot apply further customizations using a block or the fluent
-
# interface.
-
#
-
# @example
-
# allow(obj).to receive_messages(:speak => "Hello World")
-
# allow(obj).to receive_messages(:speak => "Hello", :meow => "Meow")
-
#
-
# @note If you disable the `:expect` syntax this method will be undefined.
-
-
# @method receive_message_chain
-
# @overload receive_message_chain(method1, method2)
-
# @overload receive_message_chain("method1.method2")
-
# @overload receive_message_chain(method1, method_to_value_hash)
-
#
-
# stubs/mocks a chain of messages on an object or test double.
-
#
-
# ## Warning:
-
#
-
# Chains can be arbitrarily long, which makes it quite painless to
-
# violate the Law of Demeter in violent ways, so you should consider any
-
# use of `receive_message_chain` a code smell. Even though not all code smells
-
# indicate real problems (think fluent interfaces), `receive_message_chain` still
-
# results in brittle examples. For example, if you write
-
# `allow(foo).to receive_message_chain(:bar, :baz => 37)` in a spec and then the
-
# implementation calls `foo.baz.bar`, the stub will not work.
-
#
-
# @example
-
# allow(double).to receive_message_chain("foo.bar") { :baz }
-
# allow(double).to receive_message_chain(:foo, :bar => :baz)
-
# allow(double).to receive_message_chain(:foo, :bar) { :baz }
-
#
-
# # Given any of ^^ these three forms ^^:
-
# double.foo.bar # => :baz
-
#
-
# # Common use in Rails/ActiveRecord:
-
# allow(Article).to receive_message_chain("recent.published") { [Article.new] }
-
#
-
# @note If you disable the `:expect` syntax this method will be undefined.
-
-
# @private
-
1
def self.included(klass)
-
1
klass.class_exec do
-
# This gets mixed in so that if `RSpec::Matchers` is included in
-
# `klass` later, it's definition of `expect` will take precedence.
-
1
include ExpectHost unless method_defined?(:expect)
-
end
-
end
-
-
# @private
-
1
def self.extended(object)
-
# This gets extended in so that if `RSpec::Matchers` is included in
-
# `klass` later, it's definition of `expect` will take precedence.
-
object.extend ExpectHost unless object.respond_to?(:expect)
-
end
-
-
# @private
-
1
def self.declare_verifying_double(type, ref, *args)
-
if RSpec::Mocks.configuration.verify_doubled_constant_names? &&
-
!ref.defined?
-
-
RSpec::Mocks.error_generator.raise_verifying_double_not_defined_error(ref)
-
end
-
-
RSpec::Mocks.configuration.verifying_double_callbacks.each do |block|
-
block.call(ref)
-
end
-
-
declare_double(type, ref, *args)
-
end
-
-
# @private
-
1
def self.declare_double(type, *args)
-
args << {} unless Hash === args.last
-
type.new(*args)
-
end
-
-
# This module exists to host the `expect` method for cases where
-
# rspec-mocks is used w/o rspec-expectations.
-
1
module ExpectHost
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class InstanceMethodStasher
-
1
def initialize(object, method)
-
@object = object
-
@method = method
-
@klass = (class << object; self; end)
-
-
@original_method = nil
-
@method_is_stashed = false
-
end
-
-
1
attr_reader :original_method
-
-
1
if RUBY_VERSION.to_f < 1.9
-
# @private
-
def method_is_stashed?
-
@method_is_stashed
-
end
-
-
# @private
-
def stash
-
return if !method_defined_directly_on_klass? || @method_is_stashed
-
-
@klass.__send__(:alias_method, stashed_method_name, @method)
-
@method_is_stashed = true
-
end
-
-
# @private
-
def stashed_method_name
-
"obfuscated_by_rspec_mocks__#{@method}"
-
end
-
-
# @private
-
def restore
-
return unless @method_is_stashed
-
-
if @klass.__send__(:method_defined?, @method)
-
@klass.__send__(:undef_method, @method)
-
end
-
@klass.__send__(:alias_method, @method, stashed_method_name)
-
@klass.__send__(:remove_method, stashed_method_name)
-
@method_is_stashed = false
-
end
-
else
-
-
# @private
-
1
def method_is_stashed?
-
!!@original_method
-
end
-
-
# @private
-
1
def stash
-
return unless method_defined_directly_on_klass?
-
@original_method ||= ::RSpec::Support.method_handle_for(@object, @method)
-
@klass.__send__(:undef_method, @method)
-
end
-
-
# @private
-
1
def restore
-
return unless @original_method
-
-
if @klass.__send__(:method_defined?, @method)
-
@klass.__send__(:undef_method, @method)
-
end
-
-
handle_restoration_failures do
-
@klass.__send__(:define_method, @method, @original_method)
-
end
-
-
@original_method = nil
-
end
-
end
-
-
1
if RUBY_DESCRIPTION.include?('2.0.0p247') || RUBY_DESCRIPTION.include?('2.0.0p195')
-
# ruby 2.0.0-p247 and 2.0.0-p195 both have a bug that we can't work around :(.
-
# https://bugs.ruby-lang.org/issues/8686
-
def handle_restoration_failures
-
yield
-
rescue TypeError
-
RSpec.warn_with(
-
"RSpec failed to properly restore a partial double (#{@object.inspect}) " \
-
"to its original state due to a known bug in MRI 2.0.0-p195 & p247 " \
-
"(https://bugs.ruby-lang.org/issues/8686). This object may remain " \
-
"screwed up for the rest of this process. Please upgrade to 2.0.0-p353 or above.",
-
:call_site => nil, :use_spec_location_as_call_site => true
-
)
-
end
-
else
-
1
def handle_restoration_failures
-
# No known reasons for restoration to fail on other rubies.
-
yield
-
end
-
end
-
-
1
private
-
-
# @private
-
1
def method_defined_directly_on_klass?
-
method_defined_on_klass? && method_owned_by_klass?
-
end
-
-
# @private
-
1
def method_defined_on_klass?(klass=@klass)
-
MethodReference.method_defined_at_any_visibility?(klass, @method)
-
end
-
-
1
def method_owned_by_klass?
-
owner = @klass.instance_method(@method).owner
-
-
# On Ruby 2.0.0+ the owner of a method on a class which has been
-
# `prepend`ed may actually be an instance, e.g.
-
# `#<MyClass:0x007fbb94e3cd10>`, rather than the expected `MyClass`.
-
owner = owner.class unless Module === owner
-
-
# On some 1.9s (e.g. rubinius) aliased methods
-
# can report the wrong owner. Example:
-
# class MyClass
-
# class << self
-
# alias alternate_new new
-
# end
-
# end
-
#
-
# MyClass.owner(:alternate_new) returns `Class` when incorrect,
-
# but we need to consider the owner to be `MyClass` because
-
# it is not actually available on `Class` but is on `MyClass`.
-
# Hence, we verify that the owner actually has the method defined.
-
# If the given owner does not have the method defined, we assume
-
# that the method is actually owned by @klass.
-
owner == @klass || !(method_defined_on_klass?(owner))
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# A message expectation that only allows concrete return values to be set
-
# for a message. While this same effect can be achieved using a standard
-
# MessageExpectation, this version is much faster and so can be used as an
-
# optimization.
-
#
-
# @private
-
1
class SimpleMessageExpectation
-
1
def initialize(message, response, error_generator, backtrace_line=nil)
-
@message, @response, @error_generator, @backtrace_line = message.to_sym, response, error_generator, backtrace_line
-
@received = false
-
end
-
-
1
def invoke(*_)
-
@received = true
-
@response
-
end
-
-
1
def matches?(message, *_)
-
@message == message.to_sym
-
end
-
-
1
def called_max_times?
-
false
-
end
-
-
1
def verify_messages_received
-
return if @received
-
@error_generator.raise_expectation_error(
-
@message, 1, ArgumentListMatcher::MATCH_ALL, 0, nil, [], @backtrace_line
-
)
-
end
-
-
1
def unadvise(_)
-
end
-
end
-
-
# Represents an individual method stub or message expectation. The methods
-
# defined here can be used to configure how it behaves. The methods return
-
# `self` so that they can be chained together to form a fluent interface.
-
1
class MessageExpectation
-
# @!group Configuring Responses
-
-
# @overload and_return(value)
-
# @overload and_return(first_value, second_value)
-
#
-
# Tells the object to return a value when it receives the message. Given
-
# more than one value, the first value is returned the first time the
-
# message is received, the second value is returned the next time, etc,
-
# etc.
-
#
-
# If the message is received more times than there are values, the last
-
# value is received for every subsequent call.
-
#
-
# @return [nil] No further chaining is supported after this.
-
# @example
-
# allow(counter).to receive(:count).and_return(1)
-
# counter.count # => 1
-
# counter.count # => 1
-
#
-
# allow(counter).to receive(:count).and_return(1,2,3)
-
# counter.count # => 1
-
# counter.count # => 2
-
# counter.count # => 3
-
# counter.count # => 3
-
# counter.count # => 3
-
# # etc
-
1
def and_return(first_value, *values)
-
raise_already_invoked_error_if_necessary(__method__)
-
if negative?
-
raise "`and_return` is not supported with negative message expectations"
-
end
-
-
if block_given?
-
raise ArgumentError, "Implementation blocks aren't supported with `and_return`"
-
end
-
-
values.unshift(first_value)
-
@expected_received_count = [@expected_received_count, values.size].max unless ignoring_args? || (@expected_received_count == 0 && @at_least)
-
self.terminal_implementation_action = AndReturnImplementation.new(values)
-
-
nil
-
end
-
-
# Tells the object to delegate to the original unmodified method
-
# when it receives the message.
-
#
-
# @note This is only available on partial doubles.
-
#
-
# @return [nil] No further chaining is supported after this.
-
# @example
-
# expect(counter).to receive(:increment).and_call_original
-
# original_count = counter.count
-
# counter.increment
-
# expect(counter.count).to eq(original_count + 1)
-
1
def and_call_original
-
and_wrap_original do |original, *args, &block|
-
original.call(*args, &block)
-
end
-
end
-
-
# Decorates the stubbed method with the supplied block. The original
-
# unmodified method is passed to the block along with any method call
-
# arguments so you can delegate to it, whilst still being able to
-
# change what args are passed to it and/or change the return value.
-
#
-
# @note This is only available on partial doubles.
-
#
-
# @return [nil] No further chaining is supported after this.
-
# @example
-
# expect(api).to receive(:large_list).and_wrap_original do |original_method, *args, &block|
-
# original_method.call(*args, &block).first(10)
-
# end
-
1
def and_wrap_original(&block)
-
if RSpec::Mocks::TestDouble === @method_double.object
-
@error_generator.raise_only_valid_on_a_partial_double(:and_call_original)
-
else
-
warn_about_stub_override if implementation.inner_action
-
@implementation = AndWrapOriginalImplementation.new(@method_double.original_implementation_callable, block)
-
@yield_receiver_to_implementation_block = false
-
end
-
-
nil
-
end
-
-
# @overload and_raise
-
# @overload and_raise(ExceptionClass)
-
# @overload and_raise(ExceptionClass, message)
-
# @overload and_raise(exception_instance)
-
#
-
# Tells the object to raise an exception when the message is received.
-
#
-
# @return [nil] No further chaining is supported after this.
-
# @note
-
# When you pass an exception class, the MessageExpectation will raise
-
# an instance of it, creating it with `exception` and passing `message`
-
# if specified. If the exception class initializer requires more than
-
# one parameters, you must pass in an instance and not the class,
-
# otherwise this method will raise an ArgumentError exception.
-
#
-
# @example
-
# allow(car).to receive(:go).and_raise
-
# allow(car).to receive(:go).and_raise(OutOfGas)
-
# allow(car).to receive(:go).and_raise(OutOfGas, "At least 2 oz of gas needed to drive")
-
# allow(car).to receive(:go).and_raise(OutOfGas.new(2, :oz))
-
1
def and_raise(*args)
-
raise_already_invoked_error_if_necessary(__method__)
-
self.terminal_implementation_action = Proc.new { raise(*args) }
-
nil
-
end
-
-
# @overload and_throw(symbol)
-
# @overload and_throw(symbol, object)
-
#
-
# Tells the object to throw a symbol (with the object if that form is
-
# used) when the message is received.
-
#
-
# @return [nil] No further chaining is supported after this.
-
# @example
-
# allow(car).to receive(:go).and_throw(:out_of_gas)
-
# allow(car).to receive(:go).and_throw(:out_of_gas, :level => 0.1)
-
1
def and_throw(*args)
-
raise_already_invoked_error_if_necessary(__method__)
-
self.terminal_implementation_action = Proc.new { throw(*args) }
-
nil
-
end
-
-
# Tells the object to yield one or more args to a block when the message
-
# is received.
-
#
-
# @return [MessageExpectation] self, to support further chaining.
-
# @example
-
# stream.stub(:open).and_yield(StringIO.new)
-
1
def and_yield(*args, &block)
-
raise_already_invoked_error_if_necessary(__method__)
-
yield @eval_context = Object.new if block
-
-
# Initialize args to yield now that it's being used, see also: comment
-
# in constructor.
-
@args_to_yield ||= []
-
-
@args_to_yield << args
-
self.initial_implementation_action = AndYieldImplementation.new(@args_to_yield, @eval_context, @error_generator)
-
self
-
end
-
# @!endgroup
-
-
# @!group Constraining Receive Counts
-
-
# Constrain a message expectation to be received a specific number of
-
# times.
-
#
-
# @return [MessageExpectation] self, to support further chaining.
-
# @example
-
# expect(dealer).to receive(:deal_card).exactly(10).times
-
1
def exactly(n, &block)
-
raise_already_invoked_error_if_necessary(__method__)
-
self.inner_implementation_action = block
-
set_expected_received_count :exactly, n
-
self
-
end
-
-
# Constrain a message expectation to be received at least a specific
-
# number of times.
-
#
-
# @return [MessageExpectation] self, to support further chaining.
-
# @example
-
# expect(dealer).to receive(:deal_card).at_least(9).times
-
1
def at_least(n, &block)
-
raise_already_invoked_error_if_necessary(__method__)
-
set_expected_received_count :at_least, n
-
-
if n == 0
-
raise "at_least(0) has been removed, use allow(...).to receive(:message) instead"
-
end
-
-
self.inner_implementation_action = block
-
-
self
-
end
-
-
# Constrain a message expectation to be received at most a specific
-
# number of times.
-
#
-
# @return [MessageExpectation] self, to support further chaining.
-
# @example
-
# expect(dealer).to receive(:deal_card).at_most(10).times
-
1
def at_most(n, &block)
-
raise_already_invoked_error_if_necessary(__method__)
-
self.inner_implementation_action = block
-
set_expected_received_count :at_most, n
-
self
-
end
-
-
# Syntactic sugar for `exactly`, `at_least` and `at_most`
-
#
-
# @return [MessageExpectation] self, to support further chaining.
-
# @example
-
# expect(dealer).to receive(:deal_card).exactly(10).times
-
# expect(dealer).to receive(:deal_card).at_least(10).times
-
# expect(dealer).to receive(:deal_card).at_most(10).times
-
1
def times(&block)
-
self.inner_implementation_action = block
-
self
-
end
-
-
# Expect a message not to be received at all.
-
#
-
# @return [MessageExpectation] self, to support further chaining.
-
# @example
-
# expect(car).to receive(:stop).never
-
1
def never
-
error_generator.raise_double_negation_error("expect(obj)") if negative?
-
@expected_received_count = 0
-
self
-
end
-
-
# Expect a message to be received exactly one time.
-
#
-
# @return [MessageExpectation] self, to support further chaining.
-
# @example
-
# expect(car).to receive(:go).once
-
1
def once(&block)
-
self.inner_implementation_action = block
-
set_expected_received_count :exactly, 1
-
self
-
end
-
-
# Expect a message to be received exactly two times.
-
#
-
# @return [MessageExpectation] self, to support further chaining.
-
# @example
-
# expect(car).to receive(:go).twice
-
1
def twice(&block)
-
self.inner_implementation_action = block
-
set_expected_received_count :exactly, 2
-
self
-
end
-
-
# Expect a message to be received exactly three times.
-
#
-
# @return [MessageExpectation] self, to support further chaining.
-
# @example
-
# expect(car).to receive(:go).thrice
-
1
def thrice(&block)
-
self.inner_implementation_action = block
-
set_expected_received_count :exactly, 3
-
self
-
end
-
# @!endgroup
-
-
# @!group Other Constraints
-
-
# Constrains a stub or message expectation to invocations with specific
-
# arguments.
-
#
-
# With a stub, if the message might be received with other args as well,
-
# you should stub a default value first, and then stub or mock the same
-
# message using `with` to constrain to specific arguments.
-
#
-
# A message expectation will fail if the message is received with different
-
# arguments.
-
#
-
# @return [MessageExpectation] self, to support further chaining.
-
# @example
-
# allow(cart).to receive(:add) { :failure }
-
# allow(cart).to receive(:add).with(Book.new(:isbn => 1934356379)) { :success }
-
# cart.add(Book.new(:isbn => 1234567890))
-
# # => :failure
-
# cart.add(Book.new(:isbn => 1934356379))
-
# # => :success
-
#
-
# expect(cart).to receive(:add).with(Book.new(:isbn => 1934356379)) { :success }
-
# cart.add(Book.new(:isbn => 1234567890))
-
# # => failed expectation
-
# cart.add(Book.new(:isbn => 1934356379))
-
# # => passes
-
1
def with(*args, &block)
-
raise_already_invoked_error_if_necessary(__method__)
-
if args.empty?
-
raise ArgumentError,
-
"`with` must have at least one argument. Use `no_args` matcher to set the expectation of receiving no arguments."
-
end
-
-
self.inner_implementation_action = block
-
@argument_list_matcher = ArgumentListMatcher.new(*args)
-
self
-
end
-
-
# Expect messages to be received in a specific order.
-
#
-
# @return [MessageExpectation] self, to support further chaining.
-
# @example
-
# expect(api).to receive(:prepare).ordered
-
# expect(api).to receive(:run).ordered
-
# expect(api).to receive(:finish).ordered
-
1
def ordered(&block)
-
self.inner_implementation_action = block
-
additional_expected_calls.times do
-
@order_group.register(self)
-
end
-
@ordered = true
-
self
-
end
-
-
# @private
-
# Contains the parts of `MessageExpectation` that aren't part of
-
# rspec-mocks' public API. The class is very big and could really use
-
# some collaborators it delegates to for this stuff but for now this was
-
# the simplest way to split the public from private stuff to make it
-
# easier to publish the docs for the APIs we want published.
-
1
module ImplementationDetails
-
1
attr_accessor :error_generator, :implementation
-
1
attr_reader :message
-
1
attr_reader :orig_object
-
1
attr_writer :expected_received_count, :expected_from, :argument_list_matcher
-
1
protected :expected_received_count=, :expected_from=, :error_generator, :error_generator=, :implementation=
-
-
# rubocop:disable Style/ParameterLists
-
1
def initialize(error_generator, expectation_ordering, expected_from, method_double,
-
type=:expectation, opts={}, &implementation_block)
-
@error_generator = error_generator
-
@error_generator.opts = opts
-
@expected_from = expected_from
-
@method_double = method_double
-
@orig_object = @method_double.object
-
@message = @method_double.method_name
-
@actual_received_count = 0
-
@expected_received_count = type == :expectation ? 1 : :any
-
@argument_list_matcher = ArgumentListMatcher::MATCH_ALL
-
@order_group = expectation_ordering
-
@order_group.register(self) unless type == :stub
-
@expectation_type = type
-
@ordered = false
-
@at_least = @at_most = @exactly = nil
-
-
# Initialized to nil so that we don't allocate an array for every
-
# mock or stub. See also comment in `and_yield`.
-
@args_to_yield = nil
-
@eval_context = nil
-
@yield_receiver_to_implementation_block = false
-
-
@implementation = Implementation.new
-
self.inner_implementation_action = implementation_block
-
end
-
# rubocop:enable Style/ParameterLists
-
-
1
def expected_args
-
@argument_list_matcher.expected_args
-
end
-
-
1
def and_yield_receiver_to_implementation
-
@yield_receiver_to_implementation_block = true
-
self
-
end
-
-
1
def yield_receiver_to_implementation_block?
-
@yield_receiver_to_implementation_block
-
end
-
-
1
def matches?(message, *args)
-
@message == message && @argument_list_matcher.args_match?(*args)
-
end
-
-
1
def safe_invoke(parent_stub, *args, &block)
-
invoke_incrementing_actual_calls_by(1, false, parent_stub, *args, &block)
-
end
-
-
1
def invoke(parent_stub, *args, &block)
-
invoke_incrementing_actual_calls_by(1, true, parent_stub, *args, &block)
-
end
-
-
1
def invoke_without_incrementing_received_count(parent_stub, *args, &block)
-
invoke_incrementing_actual_calls_by(0, true, parent_stub, *args, &block)
-
end
-
-
1
def negative?
-
@expected_received_count == 0 && !@at_least
-
end
-
-
1
def called_max_times?
-
@expected_received_count != :any &&
-
!@at_least &&
-
@expected_received_count > 0 &&
-
@actual_received_count >= @expected_received_count
-
end
-
-
1
def matches_name_but_not_args(message, *args)
-
@message == message && !@argument_list_matcher.args_match?(*args)
-
end
-
-
1
def verify_messages_received
-
return if expected_messages_received?
-
generate_error
-
end
-
-
1
def expected_messages_received?
-
ignoring_args? || matches_exact_count? || matches_at_least_count? || matches_at_most_count?
-
end
-
-
1
def ensure_expected_ordering_received!
-
@order_group.verify_invocation_order(self) if @ordered
-
true
-
end
-
-
1
def ignoring_args?
-
@expected_received_count == :any
-
end
-
-
1
def matches_at_least_count?
-
@at_least && @actual_received_count >= @expected_received_count
-
end
-
-
1
def matches_at_most_count?
-
@at_most && @actual_received_count <= @expected_received_count
-
end
-
-
1
def matches_exact_count?
-
@expected_received_count == @actual_received_count
-
end
-
-
1
def similar_messages
-
@similar_messages ||= []
-
end
-
-
1
def advise(*args)
-
similar_messages << args
-
end
-
-
1
def unadvise(args)
-
similar_messages.delete_if { |message| args.include?(message) }
-
end
-
-
1
def generate_error
-
if similar_messages.empty?
-
@error_generator.raise_expectation_error(
-
@message, @expected_received_count, @argument_list_matcher,
-
@actual_received_count, expectation_count_type, expected_args,
-
@expected_from, exception_source_id
-
)
-
else
-
@error_generator.raise_similar_message_args_error(
-
self, @similar_messages, @expected_from
-
)
-
end
-
end
-
-
1
def raise_unexpected_message_args_error(args_for_multiple_calls)
-
@error_generator.raise_unexpected_message_args_error(self, args_for_multiple_calls, exception_source_id)
-
end
-
-
1
def expectation_count_type
-
return :at_least if @at_least
-
return :at_most if @at_most
-
nil
-
end
-
-
1
def description_for(verb)
-
@error_generator.describe_expectation(
-
verb, @message, @expected_received_count,
-
@actual_received_count, expected_args
-
)
-
end
-
-
1
def raise_out_of_order_error
-
@error_generator.raise_out_of_order_error @message
-
end
-
-
1
def additional_expected_calls
-
return 0 if @expectation_type == :stub || !@exactly
-
@expected_received_count - 1
-
end
-
-
1
def ordered?
-
@ordered
-
end
-
-
1
def negative_expectation_for?(message)
-
@message == message && negative?
-
end
-
-
1
def actual_received_count_matters?
-
@at_least || @at_most || @exactly
-
end
-
-
1
def increase_actual_received_count!
-
@actual_received_count += 1
-
end
-
-
1
private
-
-
1
def exception_source_id
-
@exception_source_id ||= "#{self.class.name} #{__id__}"
-
end
-
-
1
def invoke_incrementing_actual_calls_by(increment, allowed_to_fail, parent_stub, *args, &block)
-
args.unshift(orig_object) if yield_receiver_to_implementation_block?
-
-
if negative? || (allowed_to_fail && (@exactly || @at_most) && (@actual_received_count == @expected_received_count))
-
# args are the args we actually received, @argument_list_matcher is the
-
# list of args we were expecting
-
@error_generator.raise_expectation_error(
-
@message, @expected_received_count,
-
@argument_list_matcher,
-
@actual_received_count + increment,
-
expectation_count_type, args, nil, exception_source_id
-
)
-
end
-
-
@order_group.handle_order_constraint self
-
-
if implementation.present?
-
implementation.call(*args, &block)
-
elsif parent_stub
-
parent_stub.invoke(nil, *args, &block)
-
end
-
ensure
-
@actual_received_count += increment
-
end
-
-
1
def has_been_invoked?
-
@actual_received_count > 0
-
end
-
-
1
def raise_already_invoked_error_if_necessary(calling_customization)
-
return unless has_been_invoked?
-
-
error_generator.raise_already_invoked_error(message, calling_customization)
-
end
-
-
1
def set_expected_received_count(relativity, n)
-
@at_least = (relativity == :at_least)
-
@at_most = (relativity == :at_most)
-
@exactly = (relativity == :exactly)
-
@expected_received_count = case n
-
when Numeric then n
-
when :once then 1
-
when :twice then 2
-
when :thrice then 3
-
end
-
end
-
-
1
def initial_implementation_action=(action)
-
implementation.initial_action = action
-
end
-
-
1
def inner_implementation_action=(action)
-
return unless action
-
warn_about_stub_override if implementation.inner_action
-
implementation.inner_action = action
-
end
-
-
1
def terminal_implementation_action=(action)
-
implementation.terminal_action = action
-
end
-
-
1
def warn_about_stub_override
-
RSpec.warning(
-
"You're overriding a previous stub implementation of `#{@message}`. " \
-
"Called from #{CallerFilter.first_non_rspec_line}."
-
)
-
end
-
end
-
-
1
include ImplementationDetails
-
end
-
-
# Handles the implementation of an `and_yield` declaration.
-
# @private
-
1
class AndYieldImplementation
-
1
def initialize(args_to_yield, eval_context, error_generator)
-
@args_to_yield = args_to_yield
-
@eval_context = eval_context
-
@error_generator = error_generator
-
end
-
-
1
def call(*_args_to_ignore, &block)
-
return if @args_to_yield.empty? && @eval_context.nil?
-
-
@error_generator.raise_missing_block_error @args_to_yield unless block
-
value = nil
-
block_signature = Support::BlockSignature.new(block)
-
-
@args_to_yield.each do |args|
-
unless Support::StrictSignatureVerifier.new(block_signature, args).valid?
-
@error_generator.raise_wrong_arity_error(args, block_signature)
-
end
-
-
value = @eval_context ? @eval_context.instance_exec(*args, &block) : block.call(*args)
-
end
-
value
-
end
-
end
-
-
# Handles the implementation of an `and_return` implementation.
-
# @private
-
1
class AndReturnImplementation
-
1
def initialize(values_to_return)
-
@values_to_return = values_to_return
-
end
-
-
1
def call(*_args_to_ignore, &_block)
-
if @values_to_return.size > 1
-
@values_to_return.shift
-
else
-
@values_to_return.first
-
end
-
end
-
end
-
-
# Represents a configured implementation. Takes into account
-
# any number of sub-implementations.
-
# @private
-
1
class Implementation
-
1
attr_accessor :initial_action, :inner_action, :terminal_action
-
-
1
def call(*args, &block)
-
actions.map do |action|
-
action.call(*args, &block)
-
end.last
-
end
-
-
1
def present?
-
actions.any?
-
end
-
-
1
private
-
-
1
def actions
-
[initial_action, inner_action, terminal_action].compact
-
end
-
end
-
-
# Represents an `and_call_original` implementation.
-
# @private
-
1
class AndWrapOriginalImplementation
-
1
def initialize(method, block)
-
@method = method
-
@block = block
-
end
-
-
1
CannotModifyFurtherError = Class.new(StandardError)
-
-
1
def initial_action=(_value)
-
raise cannot_modify_further_error
-
end
-
-
1
def inner_action=(_value)
-
raise cannot_modify_further_error
-
end
-
-
1
def terminal_action=(_value)
-
raise cannot_modify_further_error
-
end
-
-
1
def present?
-
true
-
end
-
-
1
def inner_action
-
true
-
end
-
-
1
def call(*args, &block)
-
@block.call(@method, *args, &block)
-
end
-
-
1
private
-
-
1
def cannot_modify_further_error
-
CannotModifyFurtherError.new "This method has already been configured " \
-
"to call the original implementation, and cannot be modified further."
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class MethodDouble
-
# @private
-
1
attr_reader :method_name, :object, :expectations, :stubs, :method_stasher
-
-
# @private
-
1
def initialize(object, method_name, proxy)
-
@method_name = method_name
-
@object = object
-
@proxy = proxy
-
-
@original_visibility = nil
-
@method_stasher = InstanceMethodStasher.new(object, method_name)
-
@method_is_proxied = false
-
@expectations = []
-
@stubs = []
-
end
-
-
1
def original_implementation_callable
-
# If original method is not present, uses the `method_missing`
-
# handler of the object. This accounts for cases where the user has not
-
# correctly defined `respond_to?`, and also 1.8 which does not provide
-
# method handles for missing methods even if `respond_to?` is correct.
-
@original_implementation_callable ||= original_method ||
-
Proc.new do |*args, &block|
-
@object.__send__(:method_missing, @method_name, *args, &block)
-
end
-
end
-
-
1
alias_method :save_original_implementation_callable!, :original_implementation_callable
-
-
1
def original_method
-
@original_method ||=
-
@method_stasher.original_method ||
-
@proxy.original_method_handle_for(method_name)
-
end
-
-
# @private
-
1
def visibility
-
@proxy.visibility_for(@method_name)
-
end
-
-
# @private
-
1
def object_singleton_class
-
class << @object; self; end
-
end
-
-
# @private
-
1
def configure_method
-
@original_visibility = visibility
-
@method_stasher.stash unless @method_is_proxied
-
define_proxy_method
-
end
-
-
# @private
-
1
def define_proxy_method
-
return if @method_is_proxied
-
-
save_original_implementation_callable!
-
definition_target.class_exec(self, method_name, visibility) do |method_double, method_name, visibility|
-
define_method(method_name) do |*args, &block|
-
method_double.proxy_method_invoked(self, *args, &block)
-
end
-
__send__(visibility, method_name)
-
end
-
-
@method_is_proxied = true
-
end
-
-
# The implementation of the proxied method. Subclasses may override this
-
# method to perform additional operations.
-
#
-
# @private
-
1
def proxy_method_invoked(_obj, *args, &block)
-
@proxy.message_received method_name, *args, &block
-
end
-
-
# @private
-
1
def restore_original_method
-
return show_frozen_warning if object_singleton_class.frozen?
-
return unless @method_is_proxied
-
-
remove_method_from_definition_target
-
@method_stasher.restore if @method_stasher.method_is_stashed?
-
restore_original_visibility
-
-
@method_is_proxied = false
-
end
-
-
# @private
-
1
def show_frozen_warning
-
RSpec.warn_with(
-
"WARNING: rspec-mocks was unable to restore the original `#{@method_name}` " \
-
"method on #{@object.inspect} because it has been frozen. If you reuse this " \
-
"object, `#{@method_name}` will continue to respond with its stub implementation.",
-
:call_site => nil,
-
:use_spec_location_as_call_site => true
-
)
-
end
-
-
# @private
-
1
def restore_original_visibility
-
return unless @original_visibility &&
-
MethodReference.method_defined_at_any_visibility?(object_singleton_class, @method_name)
-
-
object_singleton_class.__send__(@original_visibility, method_name)
-
end
-
-
# @private
-
1
def verify
-
expectations.each { |e| e.verify_messages_received }
-
end
-
-
# @private
-
1
def reset
-
restore_original_method
-
clear
-
end
-
-
# @private
-
1
def clear
-
expectations.clear
-
stubs.clear
-
end
-
-
# The type of message expectation to create has been extracted to its own
-
# method so that subclasses can override it.
-
#
-
# @private
-
1
def message_expectation_class
-
MessageExpectation
-
end
-
-
# @private
-
1
def add_expectation(error_generator, expectation_ordering, expected_from, opts, &implementation)
-
configure_method
-
expectation = message_expectation_class.new(error_generator, expectation_ordering,
-
expected_from, self, :expectation, opts, &implementation)
-
expectations << expectation
-
expectation
-
end
-
-
# @private
-
1
def build_expectation(error_generator, expectation_ordering)
-
expected_from = IGNORED_BACKTRACE_LINE
-
message_expectation_class.new(error_generator, expectation_ordering, expected_from, self)
-
end
-
-
# @private
-
1
def add_stub(error_generator, expectation_ordering, expected_from, opts={}, &implementation)
-
configure_method
-
stub = message_expectation_class.new(error_generator, expectation_ordering, expected_from,
-
self, :stub, opts, &implementation)
-
stubs.unshift stub
-
stub
-
end
-
-
# A simple stub can only return a concrete value for a message, and
-
# cannot match on arguments. It is used as an optimization over
-
# `add_stub` / `add_expectation` where it is known in advance that this
-
# is all that will be required of a stub, such as when passing attributes
-
# to the `double` example method. They do not stash or restore existing method
-
# definitions.
-
#
-
# @private
-
1
def add_simple_stub(method_name, response)
-
setup_simple_method_double method_name, response, stubs
-
end
-
-
# @private
-
1
def add_simple_expectation(method_name, response, error_generator, backtrace_line)
-
setup_simple_method_double method_name, response, expectations, error_generator, backtrace_line
-
end
-
-
# @private
-
1
def setup_simple_method_double(method_name, response, collection, error_generator=nil, backtrace_line=nil)
-
define_proxy_method
-
-
me = SimpleMessageExpectation.new(method_name, response, error_generator, backtrace_line)
-
collection.unshift me
-
me
-
end
-
-
# @private
-
1
def add_default_stub(*args, &implementation)
-
return if stubs.any?
-
add_stub(*args, &implementation)
-
end
-
-
# @private
-
1
def remove_stub
-
raise_method_not_stubbed_error if stubs.empty?
-
remove_stub_if_present
-
end
-
-
# @private
-
1
def remove_stub_if_present
-
expectations.empty? ? reset : stubs.clear
-
end
-
-
# @private
-
1
def raise_method_not_stubbed_error
-
RSpec::Mocks.error_generator.raise_method_not_stubbed_error(method_name)
-
end
-
-
# In Ruby 2.0.0 and above prepend will alter the method lookup chain.
-
# We use an object's singleton class to define method doubles upon,
-
# however if the object has had it's singleton class (as opposed to
-
# it's actual class) prepended too then the the method lookup chain
-
# will look in the prepended module first, **before** the singleton
-
# class.
-
#
-
# This code works around that by providing a mock definition target
-
# that is either the singleton class, or if necessary, a prepended module
-
# of our own.
-
#
-
1
if Support::RubyFeatures.module_prepends_supported?
-
-
1
private
-
-
# We subclass `Module` in order to be able to easily detect our prepended module.
-
1
RSpecPrependedModule = Class.new(Module)
-
-
1
def definition_target
-
@definition_target ||= usable_rspec_prepended_module || object_singleton_class
-
end
-
-
1
def usable_rspec_prepended_module
-
@proxy.prepended_modules_of_singleton_class.each do |mod|
-
# If we have one of our modules prepended before one of the user's
-
# modules that defines the method, use that, since our module's
-
# definition will take precedence.
-
return mod if RSpecPrependedModule === mod
-
-
# If we hit a user module with the method defined first,
-
# we must create a new prepend module, even if one exists later,
-
# because ours will only take precedence if it comes first.
-
return new_rspec_prepended_module if mod.method_defined?(method_name)
-
end
-
-
nil
-
end
-
-
1
def new_rspec_prepended_module
-
RSpecPrependedModule.new.tap do |mod|
-
object_singleton_class.__send__ :prepend, mod
-
end
-
end
-
-
else
-
-
private
-
-
def definition_target
-
object_singleton_class
-
end
-
-
end
-
-
1
private
-
-
1
def remove_method_from_definition_target
-
definition_target.__send__(:remove_method, @method_name)
-
rescue NameError
-
# This can happen when the method has been monkeyed with by
-
# something outside RSpec. This happens, for example, when
-
# `file.write` has been stubbed, and then `file.reopen(other_io)`
-
# is later called, as `File#reopen` appears to redefine `write`.
-
#
-
# Note: we could avoid rescuing this by checking
-
# `definition_target.instance_method(@method_name).owner == definition_target`,
-
# saving us from the cost of the expensive exception, but this error is
-
# extremely rare (it was discovered on 2014-12-30, only happens on
-
# RUBY_VERSION < 2.0 and our spec suite only hits this condition once),
-
# so we'd rather avoid the cost of that check for every method double,
-
# and risk the rare situation where this exception will get raised.
-
RSpec.warn_with(
-
"WARNING: RSpec could not fully restore #{@object.inspect}." \
-
"#{@method_name}, possibly because the method has been redefined " \
-
"by something outside of RSpec."
-
)
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# Represents a method on an object that may or may not be defined.
-
# The method may be an instance method on a module or a method on
-
# any object.
-
#
-
# @private
-
1
class MethodReference
-
1
def self.for(object_reference, method_name)
-
new(object_reference, method_name)
-
end
-
-
1
def initialize(object_reference, method_name)
-
@object_reference = object_reference
-
@method_name = method_name
-
end
-
-
# A method is implemented if sending the message does not result in
-
# a `NoMethodError`. It might be dynamically implemented by
-
# `method_missing`.
-
1
def implemented?
-
@object_reference.when_loaded do |m|
-
method_implemented?(m)
-
end
-
end
-
-
# Returns true if we definitively know that sending the method
-
# will result in a `NoMethodError`.
-
#
-
# This is not simply the inverse of `implemented?`: there are
-
# cases when we don't know if a method is implemented and
-
# both `implemented?` and `unimplemented?` will return false.
-
1
def unimplemented?
-
@object_reference.when_loaded do |_m|
-
return !implemented?
-
end
-
-
# If it's not loaded, then it may be implemented but we can't check.
-
false
-
end
-
-
# A method is defined if we are able to get a `Method` object for it.
-
# In that case, we can assert against metadata like the arity.
-
1
def defined?
-
@object_reference.when_loaded do |m|
-
method_defined?(m)
-
end
-
end
-
-
1
def with_signature
-
return unless (original = original_method)
-
yield Support::MethodSignature.new(original)
-
end
-
-
1
def visibility
-
@object_reference.when_loaded do |m|
-
return visibility_from(m)
-
end
-
-
# When it's not loaded, assume it's public. We don't want to
-
# wrongly treat the method as private.
-
:public
-
end
-
-
1
private
-
-
1
def original_method
-
@object_reference.when_loaded do |m|
-
self.defined? && find_method(m)
-
end
-
end
-
-
1
def self.instance_method_visibility_for(klass, method_name)
-
if klass.public_method_defined?(method_name)
-
:public
-
elsif klass.private_method_defined?(method_name)
-
:private
-
elsif klass.protected_method_defined?(method_name)
-
:protected
-
end
-
end
-
-
1
class << self
-
1
alias method_defined_at_any_visibility? instance_method_visibility_for
-
end
-
-
1
def self.method_visibility_for(object, method_name)
-
instance_method_visibility_for(class << object; self; end, method_name).tap do |vis|
-
# If the method is not defined on the class, `instance_method_visibility_for`
-
# returns `nil`. However, it may be handled dynamically by `method_missing`,
-
# so here we check `respond_to` (passing false to not check private methods).
-
#
-
# This only considers the public case, but I don't think it's possible to
-
# write `method_missing` in such a way that it handles a dynamic message
-
# with private or protected visibility. Ruby doesn't provide you with
-
# the caller info.
-
return :public if vis.nil? && object.respond_to?(method_name, false)
-
end
-
end
-
end
-
-
# @private
-
1
class InstanceMethodReference < MethodReference
-
1
private
-
-
1
def method_implemented?(mod)
-
MethodReference.method_defined_at_any_visibility?(mod, @method_name)
-
end
-
-
# Ideally, we'd use `respond_to?` for `method_implemented?` but we need a
-
# reference to an instance to do that and we don't have one. Note that
-
# we may get false negatives: if the method is implemented via
-
# `method_missing`, we'll return `false` even though it meets our
-
# definition of "implemented". However, it's the best we can do.
-
1
alias method_defined? method_implemented?
-
-
# works around the fact that repeated calls for method parameters will
-
# falsely return empty arrays on JRuby in certain circumstances, this
-
# is necessary here because we can't dup/clone UnboundMethods.
-
#
-
# This is necessary due to a bug in JRuby prior to 1.7.5 fixed in:
-
# https://github.com/jruby/jruby/commit/99a0613fe29935150d76a9a1ee4cf2b4f63f4a27
-
1
if RUBY_PLATFORM == 'java' && JRUBY_VERSION.split('.')[-1].to_i < 5
-
def find_method(mod)
-
mod.dup.instance_method(@method_name)
-
end
-
else
-
1
def find_method(mod)
-
mod.instance_method(@method_name)
-
end
-
end
-
-
1
def visibility_from(mod)
-
MethodReference.instance_method_visibility_for(mod, @method_name)
-
end
-
end
-
-
# @private
-
1
class ObjectMethodReference < MethodReference
-
1
def self.for(object_reference, method_name)
-
if ClassNewMethodReference.applies_to?(method_name) { object_reference.when_loaded { |o| o } }
-
ClassNewMethodReference.new(object_reference, method_name)
-
else
-
super
-
end
-
end
-
-
1
private
-
-
1
def method_implemented?(object)
-
object.respond_to?(@method_name, true)
-
end
-
-
1
def method_defined?(object)
-
(class << object; self; end).method_defined?(@method_name)
-
end
-
-
1
def find_method(object)
-
object.method(@method_name)
-
end
-
-
1
def visibility_from(object)
-
MethodReference.method_visibility_for(object, @method_name)
-
end
-
end
-
-
# When a class's `.new` method is stubbed, we want to use the method
-
# signature from `#initialize` because `.new`'s signature is a generic
-
# `def new(*args)` and it simply delegates to `#initialize` and forwards
-
# all args...so the method with the actually used signature is `#initialize`.
-
#
-
# This method reference implementation handles that specific case.
-
# @private
-
1
class ClassNewMethodReference < ObjectMethodReference
-
1
def self.applies_to?(method_name)
-
return false unless method_name == :new
-
klass = yield
-
return false unless klass.respond_to?(:new, true)
-
-
# We only want to apply our special logic to normal `new` methods.
-
# Methods that the user has monkeyed with should be left as-is.
-
klass.method(:new).owner == ::Class
-
end
-
-
1
def with_signature
-
@object_reference.when_loaded do |klass|
-
yield Support::MethodSignature.new(klass.instance_method(:initialize))
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support 'recursive_const_methods'
-
-
1
module RSpec
-
1
module Mocks
-
# Provides information about constants that may (or may not)
-
# have been mutated by rspec-mocks.
-
1
class Constant
-
1
extend Support::RecursiveConstMethods
-
-
# @api private
-
1
def initialize(name)
-
@name = name
-
@previously_defined = false
-
@stubbed = false
-
@hidden = false
-
@valid_name = true
-
yield self if block_given?
-
end
-
-
# @return [String] The fully qualified name of the constant.
-
1
attr_reader :name
-
-
# @return [Object, nil] The original value (e.g. before it
-
# was mutated by rspec-mocks) of the constant, or
-
# nil if the constant was not previously defined.
-
1
attr_accessor :original_value
-
-
# @private
-
1
attr_writer :previously_defined, :stubbed, :hidden, :valid_name
-
-
# @return [Boolean] Whether or not the constant was defined
-
# before the current example.
-
1
def previously_defined?
-
@previously_defined
-
end
-
-
# @return [Boolean] Whether or not rspec-mocks has mutated
-
# (stubbed or hidden) this constant.
-
1
def mutated?
-
@stubbed || @hidden
-
end
-
-
# @return [Boolean] Whether or not rspec-mocks has stubbed
-
# this constant.
-
1
def stubbed?
-
@stubbed
-
end
-
-
# @return [Boolean] Whether or not rspec-mocks has hidden
-
# this constant.
-
1
def hidden?
-
@hidden
-
end
-
-
# @return [Boolean] Whether or not the provided constant name
-
# is a valid Ruby constant name.
-
1
def valid_name?
-
@valid_name
-
end
-
-
# The default `to_s` isn't very useful, so a custom version is provided.
-
1
def to_s
-
"#<#{self.class.name} #{name}>"
-
end
-
1
alias inspect to_s
-
-
# @private
-
1
def self.unmutated(name)
-
previously_defined = recursive_const_defined?(name)
-
rescue NameError
-
new(name) do |c|
-
c.valid_name = false
-
end
-
else
-
new(name) do |const|
-
const.previously_defined = previously_defined
-
const.original_value = recursive_const_get(name) if previously_defined
-
end
-
end
-
-
# Queries rspec-mocks to find out information about the named constant.
-
#
-
# @param [String] name the name of the constant
-
# @return [Constant] an object contaning information about the named
-
# constant.
-
1
def self.original(name)
-
mutator = ::RSpec::Mocks.space.constant_mutator_for(name)
-
mutator ? mutator.to_constant : unmutated(name)
-
end
-
end
-
-
# Provides a means to stub constants.
-
1
class ConstantMutator
-
1
extend Support::RecursiveConstMethods
-
-
# Stubs a constant.
-
#
-
# @param (see ExampleMethods#stub_const)
-
# @option (see ExampleMethods#stub_const)
-
# @return (see ExampleMethods#stub_const)
-
#
-
# @see ExampleMethods#stub_const
-
# @note It's recommended that you use `stub_const` in your
-
# examples. This is an alternate public API that is provided
-
# so you can stub constants in other contexts (e.g. helper
-
# classes).
-
1
def self.stub(constant_name, value, options={})
-
mutator = if recursive_const_defined?(constant_name, &raise_on_invalid_const)
-
DefinedConstantReplacer
-
else
-
UndefinedConstantSetter
-
end
-
-
mutate(mutator.new(constant_name, value, options[:transfer_nested_constants]))
-
value
-
end
-
-
# Hides a constant.
-
#
-
# @param (see ExampleMethods#hide_const)
-
#
-
# @see ExampleMethods#hide_const
-
# @note It's recommended that you use `hide_const` in your
-
# examples. This is an alternate public API that is provided
-
# so you can hide constants in other contexts (e.g. helper
-
# classes).
-
1
def self.hide(constant_name)
-
mutate(ConstantHider.new(constant_name, nil, {}))
-
nil
-
end
-
-
# Contains common functionality used by all of the constant mutators.
-
#
-
# @private
-
1
class BaseMutator
-
1
include Support::RecursiveConstMethods
-
-
1
attr_reader :original_value, :full_constant_name
-
-
1
def initialize(full_constant_name, mutated_value, transfer_nested_constants)
-
@full_constant_name = normalize_const_name(full_constant_name)
-
@mutated_value = mutated_value
-
@transfer_nested_constants = transfer_nested_constants
-
@context_parts = @full_constant_name.split('::')
-
@const_name = @context_parts.pop
-
@reset_performed = false
-
end
-
-
1
def to_constant
-
const = Constant.new(full_constant_name)
-
const.original_value = original_value
-
-
const
-
end
-
-
1
def idempotently_reset
-
reset unless @reset_performed
-
@reset_performed = true
-
end
-
end
-
-
# Hides a defined constant for the duration of an example.
-
#
-
# @private
-
1
class ConstantHider < BaseMutator
-
1
def mutate
-
return unless (@defined = recursive_const_defined?(full_constant_name))
-
@context = recursive_const_get(@context_parts.join('::'))
-
@original_value = get_const_defined_on(@context, @const_name)
-
-
@context.__send__(:remove_const, @const_name)
-
end
-
-
1
def to_constant
-
return Constant.unmutated(full_constant_name) unless @defined
-
-
const = super
-
const.hidden = true
-
const.previously_defined = true
-
-
const
-
end
-
-
1
def reset
-
return unless @defined
-
@context.const_set(@const_name, @original_value)
-
end
-
end
-
-
# Replaces a defined constant for the duration of an example.
-
#
-
# @private
-
1
class DefinedConstantReplacer < BaseMutator
-
1
def initialize(*args)
-
super
-
@constants_to_transfer = []
-
end
-
-
1
def mutate
-
@context = recursive_const_get(@context_parts.join('::'))
-
@original_value = get_const_defined_on(@context, @const_name)
-
-
@constants_to_transfer = verify_constants_to_transfer!
-
-
@context.__send__(:remove_const, @const_name)
-
@context.const_set(@const_name, @mutated_value)
-
-
transfer_nested_constants
-
end
-
-
1
def to_constant
-
const = super
-
const.stubbed = true
-
const.previously_defined = true
-
-
const
-
end
-
-
1
def reset
-
@constants_to_transfer.each do |const|
-
@mutated_value.__send__(:remove_const, const)
-
end
-
-
@context.__send__(:remove_const, @const_name)
-
@context.const_set(@const_name, @original_value)
-
end
-
-
1
def transfer_nested_constants
-
@constants_to_transfer.each do |const|
-
@mutated_value.const_set(const, get_const_defined_on(original_value, const))
-
end
-
end
-
-
1
def verify_constants_to_transfer!
-
return [] unless should_transfer_nested_constants?
-
-
{ @original_value => "the original value", @mutated_value => "the stubbed value" }.each do |value, description|
-
next if value.respond_to?(:constants)
-
-
raise ArgumentError,
-
"Cannot transfer nested constants for #{@full_constant_name} " \
-
"since #{description} is not a class or module and only classes " \
-
"and modules support nested constants."
-
end
-
-
if Array === @transfer_nested_constants
-
@transfer_nested_constants = @transfer_nested_constants.map(&:to_s) if RUBY_VERSION == '1.8.7'
-
undefined_constants = @transfer_nested_constants - constants_defined_on(@original_value)
-
-
if undefined_constants.any?
-
available_constants = constants_defined_on(@original_value) - @transfer_nested_constants
-
raise ArgumentError,
-
"Cannot transfer nested constant(s) #{undefined_constants.join(' and ')} " \
-
"for #{@full_constant_name} since they are not defined. Did you mean " \
-
"#{available_constants.join(' or ')}?"
-
end
-
-
@transfer_nested_constants
-
else
-
constants_defined_on(@original_value)
-
end
-
end
-
-
1
def should_transfer_nested_constants?
-
return true if @transfer_nested_constants
-
return false unless RSpec::Mocks.configuration.transfer_nested_constants?
-
@original_value.respond_to?(:constants) && @mutated_value.respond_to?(:constants)
-
end
-
end
-
-
# Sets an undefined constant for the duration of an example.
-
#
-
# @private
-
1
class UndefinedConstantSetter < BaseMutator
-
1
def mutate
-
@parent = @context_parts.inject(Object) do |klass, name|
-
if const_defined_on?(klass, name)
-
get_const_defined_on(klass, name)
-
else
-
ConstantMutator.stub(name_for(klass, name), Module.new)
-
end
-
end
-
-
@parent.const_set(@const_name, @mutated_value)
-
end
-
-
1
def to_constant
-
const = super
-
const.stubbed = true
-
const.previously_defined = false
-
-
const
-
end
-
-
1
def reset
-
@parent.__send__(:remove_const, @const_name)
-
end
-
-
1
private
-
-
1
def name_for(parent, name)
-
root = if parent == Object
-
''
-
else
-
parent.name
-
end
-
root + '::' + name
-
end
-
end
-
-
# Uses the mutator to mutate (stub or hide) a constant. Ensures that
-
# the mutator is correctly registered so it can be backed out at the end
-
# of the test.
-
#
-
# @private
-
1
def self.mutate(mutator)
-
::RSpec::Mocks.space.register_constant_mutator(mutator)
-
mutator.mutate
-
end
-
-
# Used internally by the constant stubbing to raise a helpful
-
# error when a constant like "A::B::C" is stubbed and A::B is
-
# not a module (and thus, it's impossible to define "A::B::C"
-
# since only modules can have nested constants).
-
#
-
# @api private
-
1
def self.raise_on_invalid_const
-
lambda do |const_name, failed_name|
-
raise "Cannot stub constant #{failed_name} on #{const_name} " \
-
"since #{const_name} is not a module."
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class ObjectReference
-
# Returns an appropriate Object or Module reference based
-
# on the given argument.
-
1
def self.for(object_module_or_name, allow_direct_object_refs=false)
-
case object_module_or_name
-
when Module
-
if anonymous_module?(object_module_or_name)
-
DirectObjectReference.new(object_module_or_name)
-
else
-
# Use a `NamedObjectReference` if it has a name because this
-
# will use the original value of the constant in case it has
-
# been stubbed.
-
NamedObjectReference.new(name_of(object_module_or_name))
-
end
-
when String
-
NamedObjectReference.new(object_module_or_name)
-
else
-
if allow_direct_object_refs
-
DirectObjectReference.new(object_module_or_name)
-
else
-
raise ArgumentError,
-
"Module or String expected, got #{object_module_or_name.inspect}"
-
end
-
end
-
end
-
-
1
if Module.new.name.nil?
-
1
def self.anonymous_module?(mod)
-
!name_of(mod)
-
end
-
else # 1.8.7
-
def self.anonymous_module?(mod)
-
name_of(mod) == ""
-
end
-
end
-
1
private_class_method :anonymous_module?
-
-
1
def self.name_of(mod)
-
MODULE_NAME_METHOD.bind(mod).call
-
end
-
1
private_class_method :name_of
-
-
# @private
-
1
MODULE_NAME_METHOD = Module.instance_method(:name)
-
end
-
-
# An implementation of rspec-mocks' reference interface.
-
# Used when an object is passed to {ExampleMethods#object_double}, or
-
# an anonymous class or module is passed to {ExampleMethods#instance_double}
-
# or {ExampleMethods#class_double}.
-
# Represents a reference to that object.
-
# @see NamedObjectReference
-
1
class DirectObjectReference
-
# @param object [Object] the object to which this refers
-
1
def initialize(object)
-
@object = object
-
end
-
-
# @return [String] the object's description (via `#inspect`).
-
1
def description
-
@object.inspect
-
end
-
-
# Defined for interface parity with the other object reference
-
# implementations. Raises an `ArgumentError` to indicate that `as_stubbed_const`
-
# is invalid when passing an object argument to `object_double`.
-
1
def const_to_replace
-
raise ArgumentError,
-
"Can not perform constant replacement with an anonymous object."
-
end
-
-
# The target of the verifying double (the object itself).
-
#
-
# @return [Object]
-
1
def target
-
@object
-
end
-
-
# Always returns true for an object as the class is defined.
-
#
-
# @return [true]
-
1
def defined?
-
true
-
end
-
-
# Yields if the reference target is loaded, providing a generic mechanism
-
# to optionally run a bit of code only when a reference's target is
-
# loaded.
-
#
-
# This specific implementation always yields because direct references
-
# are always loaded.
-
#
-
# @yield [Object] the target of this reference.
-
1
def when_loaded
-
yield @object
-
end
-
end
-
-
# An implementation of rspec-mocks' reference interface.
-
# Used when a string is passed to {ExampleMethods#object_double},
-
# and when a string, named class or named module is passed to
-
# {ExampleMethods#instance_double}, or {ExampleMethods#class_double}.
-
# Represents a reference to the object named (via a constant lookup)
-
# by the string.
-
# @see DirectObjectReference
-
1
class NamedObjectReference
-
# @param const_name [String] constant name
-
1
def initialize(const_name)
-
@const_name = const_name
-
end
-
-
# @return [Boolean] true if the named constant is defined, false otherwise.
-
1
def defined?
-
!!object
-
end
-
-
# @return [String] the constant name to replace with a double.
-
1
def const_to_replace
-
@const_name
-
end
-
1
alias description const_to_replace
-
-
# @return [Object, nil] the target of the verifying double (the named object), or
-
# nil if it is not defined.
-
1
def target
-
object
-
end
-
-
# Yields if the reference target is loaded, providing a generic mechanism
-
# to optionally run a bit of code only when a reference's target is
-
# loaded.
-
#
-
# @yield [Object] the target object
-
1
def when_loaded
-
yield object if object
-
end
-
-
1
private
-
-
1
def object
-
return @object if defined?(@object)
-
@object = Constant.original(@const_name).original_value
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class OrderGroup
-
1
def initialize
-
9
@expectations = []
-
9
@invocation_order = []
-
9
@index = 0
-
end
-
-
# @private
-
1
def register(expectation)
-
@expectations << expectation
-
end
-
-
1
def invoked(message)
-
@invocation_order << message
-
end
-
-
# @private
-
1
def ready_for?(expectation)
-
remaining_expectations.find(&:ordered?) == expectation
-
end
-
-
# @private
-
1
def consume
-
remaining_expectations.each_with_index do |expectation, index|
-
next unless expectation.ordered?
-
-
@index += index + 1
-
return expectation
-
end
-
nil
-
end
-
-
# @private
-
1
def handle_order_constraint(expectation)
-
return unless expectation.ordered? && remaining_expectations.include?(expectation)
-
return consume if ready_for?(expectation)
-
expectation.raise_out_of_order_error
-
end
-
-
1
def verify_invocation_order(expectation)
-
expectation.raise_out_of_order_error unless expectations_invoked_in_order?
-
true
-
end
-
-
1
def clear
-
@index = 0
-
@invocation_order.clear
-
@expectations.clear
-
end
-
-
1
def empty?
-
@expectations.empty?
-
end
-
-
1
private
-
-
1
def remaining_expectations
-
@expectations[@index..-1] || []
-
end
-
-
1
def expectations_invoked_in_order?
-
invoked_expectations == expected_invocations
-
end
-
-
1
def invoked_expectations
-
@expectations.select { |e| e.ordered? && @invocation_order.include?(e) }
-
end
-
-
1
def expected_invocations
-
@invocation_order.map { |invocation| expectation_for(invocation) }.compact
-
end
-
-
1
def expectation_for(message)
-
@expectations.find { |e| message == e }
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class Proxy
-
1
SpecificMessage = Struct.new(:object, :message, :args) do
-
1
def ==(expectation)
-
expectation.orig_object == object && expectation.matches?(message, *args)
-
end
-
end
-
-
# @private
-
1
def ensure_implemented(*_args)
-
# noop for basic proxies, see VerifyingProxy for behaviour.
-
end
-
-
# @private
-
1
def initialize(object, order_group, options={})
-
@object = object
-
@order_group = order_group
-
@error_generator = ErrorGenerator.new(object)
-
@messages_received = []
-
@options = options
-
@null_object = false
-
@method_doubles = Hash.new { |h, k| h[k] = MethodDouble.new(@object, k, self) }
-
end
-
-
# @private
-
1
attr_reader :object
-
-
# @private
-
1
def null_object?
-
@null_object
-
end
-
-
# @private
-
# Tells the object to ignore any messages that aren't explicitly set as
-
# stubs or message expectations.
-
1
def as_null_object
-
@null_object = true
-
@object
-
end
-
-
# @private
-
1
def original_method_handle_for(_message)
-
nil
-
end
-
-
1
DEFAULT_MESSAGE_EXPECTATION_OPTS = {}.freeze
-
-
# @private
-
1
def add_message_expectation(method_name, opts=DEFAULT_MESSAGE_EXPECTATION_OPTS, &block)
-
location = opts.fetch(:expected_from) { CallerFilter.first_non_rspec_line }
-
meth_double = method_double_for(method_name)
-
-
if null_object? && !block
-
meth_double.add_default_stub(@error_generator, @order_group, location, opts) do
-
@object
-
end
-
end
-
-
meth_double.add_expectation @error_generator, @order_group, location, opts, &block
-
end
-
-
# @private
-
1
def add_simple_expectation(method_name, response, location)
-
method_double_for(method_name).add_simple_expectation method_name, response, @error_generator, location
-
end
-
-
# @private
-
1
def build_expectation(method_name)
-
meth_double = method_double_for(method_name)
-
-
meth_double.build_expectation(
-
@error_generator,
-
@order_group
-
)
-
end
-
-
# @private
-
1
def replay_received_message_on(expectation, &block)
-
expected_method_name = expectation.message
-
meth_double = method_double_for(expected_method_name)
-
-
if meth_double.expectations.any?
-
@error_generator.raise_expectation_on_mocked_method(expected_method_name)
-
end
-
-
unless null_object? || meth_double.stubs.any?
-
@error_generator.raise_expectation_on_unstubbed_method(expected_method_name)
-
end
-
-
@messages_received.each do |(actual_method_name, args, received_block)|
-
next unless expectation.matches?(actual_method_name, *args)
-
-
expectation.safe_invoke(nil)
-
block.call(*args, &received_block) if block
-
end
-
end
-
-
# @private
-
1
def check_for_unexpected_arguments(expectation)
-
return if @messages_received.empty?
-
-
return if @messages_received.any? { |method_name, args, _| expectation.matches?(method_name, *args) }
-
-
name_but_not_args, others = @messages_received.partition do |(method_name, args, _)|
-
expectation.matches_name_but_not_args(method_name, *args)
-
end
-
-
return if name_but_not_args.empty? && !others.empty?
-
-
expectation.raise_unexpected_message_args_error(name_but_not_args.map { |args| args[1] })
-
end
-
-
# @private
-
1
def add_stub(method_name, opts={}, &implementation)
-
location = opts.fetch(:expected_from) { CallerFilter.first_non_rspec_line }
-
method_double_for(method_name).add_stub @error_generator, @order_group, location, opts, &implementation
-
end
-
-
# @private
-
1
def add_simple_stub(method_name, response)
-
method_double_for(method_name).add_simple_stub method_name, response
-
end
-
-
# @private
-
1
def remove_stub(method_name)
-
method_double_for(method_name).remove_stub
-
end
-
-
# @private
-
1
def remove_stub_if_present(method_name)
-
method_double_for(method_name).remove_stub_if_present
-
end
-
-
# @private
-
1
def verify
-
@method_doubles.each_value { |d| d.verify }
-
end
-
-
# @private
-
1
def reset
-
@messages_received.clear
-
end
-
-
# @private
-
1
def received_message?(method_name, *args, &block)
-
@messages_received.any? { |array| array == [method_name, args, block] }
-
end
-
-
# @private
-
1
def messages_arg_list
-
@messages_received.map { |_, args, _| args }
-
end
-
-
# @private
-
1
def has_negative_expectation?(message)
-
method_double_for(message).expectations.find { |expectation| expectation.negative_expectation_for?(message) }
-
end
-
-
# @private
-
1
def record_message_received(message, *args, &block)
-
@order_group.invoked SpecificMessage.new(object, message, args)
-
@messages_received << [message, args, block]
-
end
-
-
# @private
-
1
def message_received(message, *args, &block)
-
record_message_received message, *args, &block
-
-
expectation = find_matching_expectation(message, *args)
-
stub = find_matching_method_stub(message, *args)
-
-
if (stub && expectation && expectation.called_max_times?) || (stub && !expectation)
-
expectation.increase_actual_received_count! if expectation && expectation.actual_received_count_matters?
-
if (expectation = find_almost_matching_expectation(message, *args))
-
expectation.advise(*args) unless expectation.expected_messages_received?
-
end
-
stub.invoke(nil, *args, &block)
-
elsif expectation
-
expectation.unadvise(messages_arg_list)
-
expectation.invoke(stub, *args, &block)
-
elsif (expectation = find_almost_matching_expectation(message, *args))
-
expectation.advise(*args) if null_object? unless expectation.expected_messages_received?
-
-
if null_object? || !has_negative_expectation?(message)
-
expectation.raise_unexpected_message_args_error([args])
-
end
-
elsif (stub = find_almost_matching_stub(message, *args))
-
stub.advise(*args)
-
raise_missing_default_stub_error(stub, [args])
-
elsif Class === @object
-
@object.superclass.__send__(message, *args, &block)
-
else
-
@object.__send__(:method_missing, message, *args, &block)
-
end
-
end
-
-
# @private
-
1
def raise_unexpected_message_error(method_name, args)
-
@error_generator.raise_unexpected_message_error method_name, args
-
end
-
-
# @private
-
1
def raise_missing_default_stub_error(expectation, args_for_multiple_calls)
-
@error_generator.raise_missing_default_stub_error(expectation, args_for_multiple_calls)
-
end
-
-
# @private
-
1
def visibility_for(_method_name)
-
# This is the default (for test doubles). Subclasses override this.
-
:public
-
end
-
-
1
if Support::RubyFeatures.module_prepends_supported?
-
1
def self.prepended_modules_of(klass)
-
ancestors = klass.ancestors
-
-
# `|| 0` is necessary for Ruby 2.0, where the singleton class
-
# is only in the ancestor list when there are prepended modules.
-
singleton_index = ancestors.index(klass) || 0
-
-
ancestors[0, singleton_index]
-
end
-
-
1
def prepended_modules_of_singleton_class
-
@prepended_modules_of_singleton_class ||= RSpec::Mocks::Proxy.prepended_modules_of(@object.singleton_class)
-
end
-
end
-
-
1
private
-
-
1
def method_double_for(message)
-
@method_doubles[message.to_sym]
-
end
-
-
1
def find_matching_expectation(method_name, *args)
-
find_best_matching_expectation_for(method_name) do |expectation|
-
expectation.matches?(method_name, *args)
-
end
-
end
-
-
1
def find_almost_matching_expectation(method_name, *args)
-
find_best_matching_expectation_for(method_name) do |expectation|
-
expectation.matches_name_but_not_args(method_name, *args)
-
end
-
end
-
-
1
def find_best_matching_expectation_for(method_name)
-
first_match = nil
-
-
method_double_for(method_name).expectations.each do |expectation|
-
next unless yield expectation
-
return expectation unless expectation.called_max_times?
-
first_match ||= expectation
-
end
-
-
first_match
-
end
-
-
1
def find_matching_method_stub(method_name, *args)
-
method_double_for(method_name).stubs.find { |stub| stub.matches?(method_name, *args) }
-
end
-
-
1
def find_almost_matching_stub(method_name, *args)
-
method_double_for(method_name).stubs.find { |stub| stub.matches_name_but_not_args(method_name, *args) }
-
end
-
end
-
-
# @private
-
1
class TestDoubleProxy < Proxy
-
1
def reset
-
@method_doubles.clear
-
object.__disallow_further_usage!
-
super
-
end
-
end
-
-
# @private
-
1
class PartialDoubleProxy < Proxy
-
1
def original_method_handle_for(message)
-
if any_instance_class_recorder_observing_method?(@object.class, message)
-
message = ::RSpec::Mocks.space.
-
any_instance_recorder_for(@object.class).
-
build_alias_method_name(message)
-
end
-
-
::RSpec::Support.method_handle_for(@object, message)
-
rescue NameError
-
nil
-
end
-
-
# @private
-
1
def add_simple_expectation(method_name, response, location)
-
method_double_for(method_name).configure_method
-
super
-
end
-
-
# @private
-
1
def add_simple_stub(method_name, response)
-
method_double_for(method_name).configure_method
-
super
-
end
-
-
# @private
-
1
def visibility_for(method_name)
-
# We fall back to :public because by default we allow undefined methods
-
# to be stubbed, and when we do so, we make them public.
-
MethodReference.method_visibility_for(@object, method_name) || :public
-
end
-
-
1
def reset
-
@method_doubles.each_value { |d| d.reset }
-
super
-
end
-
-
1
def message_received(message, *args, &block)
-
RSpec::Mocks.space.any_instance_recorders_from_ancestry_of(object).each do |subscriber|
-
subscriber.notify_received_message(object, message, args, block)
-
end
-
super
-
end
-
-
1
private
-
-
1
def any_instance_class_recorder_observing_method?(klass, method_name)
-
only_return_existing = true
-
recorder = ::RSpec::Mocks.space.any_instance_recorder_for(klass, only_return_existing)
-
return true if recorder && recorder.already_observing?(method_name)
-
-
superklass = klass.superclass
-
return false if superklass.nil?
-
any_instance_class_recorder_observing_method?(superklass, method_name)
-
end
-
end
-
-
# @private
-
# When we mock or stub a method on a class, we have to treat it a bit different,
-
# because normally singleton method definitions only affect the object on which
-
# they are defined, but on classes they affect subclasses, too. As a result,
-
# we need some special handling to get the original method.
-
1
module PartialClassDoubleProxyMethods
-
1
def initialize(source_space, *args)
-
@source_space = source_space
-
super(*args)
-
end
-
-
# Consider this situation:
-
#
-
# class A; end
-
# class B < A; end
-
#
-
# allow(A).to receive(:new)
-
# expect(B).to receive(:new).and_call_original
-
#
-
# When getting the original definition for `B.new`, we cannot rely purely on
-
# using `B.method(:new)` before our redefinition is defined on `B`, because
-
# `B.method(:new)` will return a method that will execute the stubbed version
-
# of the method on `A` since singleton methods on classes are in the lookup
-
# hierarchy.
-
#
-
# To do it properly, we need to find the original definition of `new` from `A`
-
# from _before_ `A` was stubbed, and we need to rebind it to `B` so that it will
-
# run with the proper `self`.
-
#
-
# That's what this method (together with `original_unbound_method_handle_from_ancestor_for`)
-
# does.
-
1
def original_method_handle_for(message)
-
unbound_method = superclass_proxy &&
-
superclass_proxy.original_unbound_method_handle_from_ancestor_for(message.to_sym)
-
-
return super unless unbound_method
-
unbound_method.bind(object)
-
# :nocov:
-
skipped
rescue TypeError
-
skipped
if RUBY_VERSION == '1.8.7'
-
skipped
# In MRI 1.8.7, a singleton method on a class cannot be rebound to its subclass
-
skipped
if unbound_method && unbound_method.owner.ancestors.first != unbound_method.owner
-
skipped
# This is a singleton method; we can't do anything with it
-
skipped
# But we can work around this using a different implementation
-
skipped
double = method_double_from_ancestor_for(message)
-
skipped
return object.method(double.method_stasher.stashed_method_name)
-
skipped
end
-
skipped
end
-
skipped
raise
-
# :nocov:
-
end
-
-
1
protected
-
-
1
def original_unbound_method_handle_from_ancestor_for(message)
-
double = method_double_from_ancestor_for(message)
-
double && double.original_method.unbind
-
end
-
-
1
def method_double_from_ancestor_for(message)
-
@method_doubles.fetch(message) do
-
# The fact that there is no method double for this message indicates
-
# that it has not been redefined by rspec-mocks. We need to continue
-
# looking up the ancestor chain.
-
return superclass_proxy &&
-
superclass_proxy.method_double_from_ancestor_for(message)
-
end
-
end
-
-
1
def superclass_proxy
-
return @superclass_proxy if defined?(@superclass_proxy)
-
-
if (superclass = object.superclass)
-
@superclass_proxy = @source_space.superclass_proxy_for(superclass)
-
else
-
@superclass_proxy = nil
-
end
-
end
-
end
-
-
# @private
-
1
class PartialClassDoubleProxy < PartialDoubleProxy
-
1
include PartialClassDoubleProxyMethods
-
end
-
-
# @private
-
1
class ProxyForNil < PartialDoubleProxy
-
1
def initialize(order_group)
-
set_expectation_behavior
-
super(nil, order_group)
-
end
-
-
1
attr_accessor :disallow_expectations
-
1
attr_accessor :warn_about_expectations
-
-
1
def add_message_expectation(method_name, opts={}, &block)
-
warn_or_raise!(method_name)
-
super
-
end
-
-
1
def add_negative_message_expectation(location, method_name, &implementation)
-
warn_or_raise!(method_name)
-
super
-
end
-
-
1
def add_stub(method_name, opts={}, &implementation)
-
warn_or_raise!(method_name)
-
super
-
end
-
-
1
private
-
-
1
def set_expectation_behavior
-
case RSpec::Mocks.configuration.allow_message_expectations_on_nil
-
when false
-
@warn_about_expectations = false
-
@disallow_expectations = true
-
when true
-
@warn_about_expectations = false
-
@disallow_expectations = false
-
else
-
@warn_about_expectations = true
-
@disallow_expectations = false
-
end
-
end
-
-
1
def warn_or_raise!(method_name)
-
# This method intentionally swallows the message when
-
# neither disallow_expectations nor warn_about_expectations
-
# are set to true.
-
if disallow_expectations
-
raise_error(method_name)
-
elsif warn_about_expectations
-
warn(method_name)
-
end
-
end
-
-
1
def warn(method_name)
-
warning_msg = @error_generator.expectation_on_nil_message(method_name)
-
RSpec.warning(warning_msg)
-
end
-
-
1
def raise_error(method_name)
-
@error_generator.raise_expectation_on_nil_error(method_name)
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support 'reentrant_mutex'
-
-
1
module RSpec
-
1
module Mocks
-
# @private
-
# Provides a default space implementation for outside
-
# the scope of an example. Called "root" because it serves
-
# as the root of the space stack.
-
1
class RootSpace
-
1
def proxy_for(*_args)
-
raise_lifecycle_message
-
end
-
-
1
def any_instance_recorder_for(*_args)
-
raise_lifecycle_message
-
end
-
-
1
def any_instance_proxy_for(*_args)
-
raise_lifecycle_message
-
end
-
-
1
def register_constant_mutator(_mutator)
-
raise_lifecycle_message
-
end
-
-
1
def any_instance_recorders_from_ancestry_of(_object)
-
raise_lifecycle_message
-
end
-
-
1
def reset_all
-
end
-
-
1
def verify_all
-
end
-
-
1
def registered?(_object)
-
false
-
end
-
-
1
def superclass_proxy_for(*_args)
-
raise_lifecycle_message
-
end
-
-
1
def new_scope
-
9
Space.new
-
end
-
-
1
private
-
-
1
def raise_lifecycle_message
-
raise OutsideOfExampleError,
-
"The use of doubles or partial doubles from rspec-mocks outside of the per-test lifecycle is not supported."
-
end
-
end
-
-
# @private
-
1
class Space
-
1
attr_reader :proxies, :any_instance_recorders, :proxy_mutex, :any_instance_mutex
-
-
1
def initialize
-
9
@proxies = {}
-
9
@any_instance_recorders = {}
-
9
@constant_mutators = []
-
9
@expectation_ordering = OrderGroup.new
-
9
@proxy_mutex = new_mutex
-
9
@any_instance_mutex = new_mutex
-
end
-
-
1
def new_scope
-
NestedSpace.new(self)
-
end
-
-
1
def verify_all
-
9
proxies.values.each { |proxy| proxy.verify }
-
9
any_instance_recorders.each_value { |recorder| recorder.verify }
-
end
-
-
1
def reset_all
-
9
proxies.each_value { |proxy| proxy.reset }
-
9
@constant_mutators.reverse.each { |mut| mut.idempotently_reset }
-
9
any_instance_recorders.each_value { |recorder| recorder.stop_all_observation! }
-
9
any_instance_recorders.clear
-
end
-
-
1
def register_constant_mutator(mutator)
-
@constant_mutators << mutator
-
end
-
-
1
def constant_mutator_for(name)
-
@constant_mutators.find { |m| m.full_constant_name == name }
-
end
-
-
1
def any_instance_recorder_for(klass, only_return_existing=false)
-
any_instance_mutex.synchronize do
-
id = klass.__id__
-
any_instance_recorders.fetch(id) do
-
return nil if only_return_existing
-
any_instance_recorder_not_found_for(id, klass)
-
end
-
end
-
end
-
-
1
def any_instance_proxy_for(klass)
-
AnyInstance::Proxy.new(any_instance_recorder_for(klass), proxies_of(klass))
-
end
-
-
1
def proxies_of(klass)
-
proxies.values.select { |proxy| klass === proxy.object }
-
end
-
-
1
def proxy_for(object)
-
proxy_mutex.synchronize do
-
id = id_for(object)
-
proxies.fetch(id) { proxy_not_found_for(id, object) }
-
end
-
end
-
-
1
def superclass_proxy_for(klass)
-
proxy_mutex.synchronize do
-
id = id_for(klass)
-
proxies.fetch(id) { superclass_proxy_not_found_for(id, klass) }
-
end
-
end
-
-
1
alias ensure_registered proxy_for
-
-
1
def registered?(object)
-
proxies.key?(id_for object)
-
end
-
-
1
def any_instance_recorders_from_ancestry_of(object)
-
# Optimization: `any_instance` is a feature we generally
-
# recommend not using, so we can often early exit here
-
# without doing an O(N) linear search over the number of
-
# ancestors in the object's class hierarchy.
-
return [] if any_instance_recorders.empty?
-
-
# We access the ancestors through the singleton class, to avoid calling
-
# `class` in case `class` has been stubbed.
-
(class << object; ancestors; end).map do |klass|
-
any_instance_recorders[klass.__id__]
-
end.compact
-
end
-
-
1
private
-
-
1
def new_mutex
-
18
Support::ReentrantMutex.new
-
end
-
-
1
def proxy_not_found_for(id, object)
-
proxies[id] = case object
-
when NilClass then ProxyForNil.new(@expectation_ordering)
-
when TestDouble then object.__build_mock_proxy_unless_expired(@expectation_ordering)
-
when Class
-
class_proxy_with_callback_verification_strategy(object, CallbackInvocationStrategy.new)
-
else
-
if RSpec::Mocks.configuration.verify_partial_doubles?
-
VerifyingPartialDoubleProxy.new(object, @expectation_ordering)
-
else
-
PartialDoubleProxy.new(object, @expectation_ordering)
-
end
-
end
-
end
-
-
1
def superclass_proxy_not_found_for(id, object)
-
raise "superclass_proxy_not_found_for called with something that is not a class" unless Class === object
-
proxies[id] = class_proxy_with_callback_verification_strategy(object, NoCallbackInvocationStrategy.new)
-
end
-
-
1
def class_proxy_with_callback_verification_strategy(object, strategy)
-
if RSpec::Mocks.configuration.verify_partial_doubles?
-
VerifyingPartialClassDoubleProxy.new(
-
self,
-
object,
-
@expectation_ordering,
-
strategy
-
)
-
else
-
PartialClassDoubleProxy.new(self, object, @expectation_ordering)
-
end
-
end
-
-
1
def any_instance_recorder_not_found_for(id, klass)
-
any_instance_recorders[id] = AnyInstance::Recorder.new(klass)
-
end
-
-
1
if defined?(::BasicObject) && !::BasicObject.method_defined?(:__id__) # for 1.9.2
-
require 'securerandom'
-
-
def id_for(object)
-
id = object.__id__
-
-
return id if object.equal?(::ObjectSpace._id2ref(id))
-
# this suggests that object.__id__ is proxying through to some wrapped object
-
-
object.instance_exec do
-
@__id_for_rspec_mocks_space ||= ::SecureRandom.uuid
-
end
-
end
-
else
-
1
def id_for(object)
-
object.__id__
-
end
-
end
-
end
-
-
# @private
-
1
class NestedSpace < Space
-
1
def initialize(parent)
-
@parent = parent
-
super()
-
end
-
-
1
def proxies_of(klass)
-
super + @parent.proxies_of(klass)
-
end
-
-
1
def constant_mutator_for(name)
-
super || @parent.constant_mutator_for(name)
-
end
-
-
1
def registered?(object)
-
super || @parent.registered?(object)
-
end
-
-
1
private
-
-
1
def proxy_not_found_for(id, object)
-
@parent.proxies[id] || super
-
end
-
-
1
def any_instance_recorder_not_found_for(id, klass)
-
@parent.any_instance_recorders[id] || super
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @api private
-
# Provides methods for enabling and disabling the available syntaxes
-
# provided by rspec-mocks.
-
1
module Syntax
-
# @private
-
1
def self.warn_about_should!
-
1
@warn_about_should = true
-
end
-
-
# @private
-
1
def self.warn_unless_should_configured(method_name , replacement="the new `:expect` syntax or explicitly enable `:should`")
-
if @warn_about_should
-
RSpec.deprecate(
-
"Using `#{method_name}` from rspec-mocks' old `:should` syntax without explicitly enabling the syntax",
-
:replacement => replacement
-
)
-
-
@warn_about_should = false
-
end
-
end
-
-
# @api private
-
# Enables the should syntax (`dbl.stub`, `dbl.should_receive`, etc).
-
1
def self.enable_should(syntax_host=default_should_syntax_host)
-
1
@warn_about_should = false if syntax_host == default_should_syntax_host
-
1
return if should_enabled?(syntax_host)
-
-
1
syntax_host.class_exec do
-
1
def should_receive(message, opts={}, &block)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
::RSpec::Mocks.expect_message(self, message, opts, &block)
-
end
-
-
1
def should_not_receive(message, &block)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
::RSpec::Mocks.expect_message(self, message, {}, &block).never
-
end
-
-
1
def stub(message_or_hash, opts={}, &block)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
if ::Hash === message_or_hash
-
message_or_hash.each { |message, value| stub(message).and_return value }
-
else
-
::RSpec::Mocks.allow_message(self, message_or_hash, opts, &block)
-
end
-
end
-
-
1
def unstub(message)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__, "`allow(...).to receive(...).and_call_original` or explicitly enable `:should`")
-
::RSpec::Mocks.space.proxy_for(self).remove_stub(message)
-
end
-
-
1
def stub_chain(*chain, &blk)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
::RSpec::Mocks::StubChain.stub_chain_on(self, *chain, &blk)
-
end
-
-
1
def as_null_object
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
@_null_object = true
-
::RSpec::Mocks.space.proxy_for(self).as_null_object
-
end
-
-
1
def null_object?
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
defined?(@_null_object)
-
end
-
-
1
def received_message?(message, *args, &block)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
::RSpec::Mocks.space.proxy_for(self).received_message?(message, *args, &block)
-
end
-
-
1
unless Class.respond_to? :any_instance
-
1
Class.class_exec do
-
1
def any_instance
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
::RSpec::Mocks.space.any_instance_proxy_for(self)
-
end
-
end
-
end
-
end
-
end
-
-
# @api private
-
# Disables the should syntax (`dbl.stub`, `dbl.should_receive`, etc).
-
1
def self.disable_should(syntax_host=default_should_syntax_host)
-
return unless should_enabled?(syntax_host)
-
-
syntax_host.class_exec do
-
undef should_receive
-
undef should_not_receive
-
undef stub
-
undef unstub
-
undef stub_chain
-
undef as_null_object
-
undef null_object?
-
undef received_message?
-
end
-
-
Class.class_exec do
-
undef any_instance
-
end
-
end
-
-
# @api private
-
# Enables the expect syntax (`expect(dbl).to receive`, `allow(dbl).to receive`, etc).
-
1
def self.enable_expect(syntax_host=::RSpec::Mocks::ExampleMethods)
-
1
return if expect_enabled?(syntax_host)
-
-
1
syntax_host.class_exec do
-
1
def receive(method_name, &block)
-
Matchers::Receive.new(method_name, block)
-
end
-
-
1
def receive_messages(message_return_value_hash)
-
matcher = Matchers::ReceiveMessages.new(message_return_value_hash)
-
matcher.warn_about_block if block_given?
-
matcher
-
end
-
-
1
def receive_message_chain(*messages, &block)
-
Matchers::ReceiveMessageChain.new(messages, &block)
-
end
-
-
1
def allow(target)
-
AllowanceTarget.new(target)
-
end
-
-
1
def expect_any_instance_of(klass)
-
AnyInstanceExpectationTarget.new(klass)
-
end
-
-
1
def allow_any_instance_of(klass)
-
AnyInstanceAllowanceTarget.new(klass)
-
end
-
end
-
-
1
RSpec::Mocks::ExampleMethods::ExpectHost.class_exec do
-
1
def expect(target)
-
ExpectationTarget.new(target)
-
end
-
end
-
end
-
-
# @api private
-
# Disables the expect syntax (`expect(dbl).to receive`, `allow(dbl).to receive`, etc).
-
1
def self.disable_expect(syntax_host=::RSpec::Mocks::ExampleMethods)
-
return unless expect_enabled?(syntax_host)
-
-
syntax_host.class_exec do
-
undef receive
-
undef receive_messages
-
undef receive_message_chain
-
undef allow
-
undef expect_any_instance_of
-
undef allow_any_instance_of
-
end
-
-
RSpec::Mocks::ExampleMethods::ExpectHost.class_exec do
-
undef expect
-
end
-
end
-
-
# @api private
-
# Indicates whether or not the should syntax is enabled.
-
1
def self.should_enabled?(syntax_host=default_should_syntax_host)
-
1
syntax_host.method_defined?(:should_receive)
-
end
-
-
# @api private
-
# Indicates whether or not the expect syntax is enabled.
-
1
def self.expect_enabled?(syntax_host=::RSpec::Mocks::ExampleMethods)
-
1
syntax_host.method_defined?(:allow)
-
end
-
-
# @api private
-
# Determines where the methods like `should_receive`, and `stub` are added.
-
1
def self.default_should_syntax_host
-
# JRuby 1.7.4 introduces a regression whereby `defined?(::BasicObject) => nil`
-
# yet `BasicObject` still exists and patching onto ::Object breaks things
-
# e.g. SimpleDelegator expectations won't work
-
#
-
# See: https://github.com/jruby/jruby/issues/814
-
2
if defined?(JRUBY_VERSION) && JRUBY_VERSION == '1.7.4' && RUBY_VERSION.to_f > 1.8
-
return ::BasicObject
-
end
-
-
# On 1.8.7, Object.ancestors.last == Kernel but
-
# things blow up if we include `RSpec::Mocks::Methods`
-
# into Kernel...not sure why.
-
2
return Object unless defined?(::BasicObject)
-
-
# MacRuby has BasicObject but it's not the root class.
-
2
return Object unless Object.ancestors.last == ::BasicObject
-
-
2
::BasicObject
-
end
-
end
-
end
-
end
-
-
1
if defined?(BasicObject)
-
# The legacy `:should` syntax adds the following methods directly to
-
# `BasicObject` so that they are available off of any object. Note, however,
-
# that this syntax does not always play nice with delegate/proxy objects.
-
# We recommend you use the non-monkeypatching `:expect` syntax instead.
-
# @see Class
-
1
class BasicObject
-
# @method should_receive
-
# Sets an expectation that this object should receive a message before
-
# the end of the example.
-
#
-
# @example
-
# logger = double('logger')
-
# thing_that_logs = ThingThatLogs.new(logger)
-
# logger.should_receive(:log)
-
# thing_that_logs.do_something_that_logs_a_message
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
# @see RSpec::Mocks::ExampleMethods#expect
-
-
# @method should_not_receive
-
# Sets and expectation that this object should _not_ receive a message
-
# during this example.
-
# @see RSpec::Mocks::ExampleMethods#expect
-
-
# @method stub
-
# Tells the object to respond to the message with the specified value.
-
#
-
# @example
-
# counter.stub(:count).and_return(37)
-
# counter.stub(:count => 37)
-
# counter.stub(:count) { 37 }
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
# @see RSpec::Mocks::ExampleMethods#allow
-
-
# @method unstub
-
# Removes a stub. On a double, the object will no longer respond to
-
# `message`. On a real object, the original method (if it exists) is
-
# restored.
-
#
-
# This is rarely used, but can be useful when a stub is set up during a
-
# shared `before` hook for the common case, but you want to replace it
-
# for a special case.
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
-
# @method stub_chain
-
# @overload stub_chain(method1, method2)
-
# @overload stub_chain("method1.method2")
-
# @overload stub_chain(method1, method_to_value_hash)
-
#
-
# Stubs a chain of methods.
-
#
-
# ## Warning:
-
#
-
# Chains can be arbitrarily long, which makes it quite painless to
-
# violate the Law of Demeter in violent ways, so you should consider any
-
# use of `stub_chain` a code smell. Even though not all code smells
-
# indicate real problems (think fluent interfaces), `stub_chain` still
-
# results in brittle examples. For example, if you write
-
# `foo.stub_chain(:bar, :baz => 37)` in a spec and then the
-
# implementation calls `foo.baz.bar`, the stub will not work.
-
#
-
# @example
-
# double.stub_chain("foo.bar") { :baz }
-
# double.stub_chain(:foo, :bar => :baz)
-
# double.stub_chain(:foo, :bar) { :baz }
-
#
-
# # Given any of ^^ these three forms ^^:
-
# double.foo.bar # => :baz
-
#
-
# # Common use in Rails/ActiveRecord:
-
# Article.stub_chain("recent.published") { [Article.new] }
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
# @see RSpec::Mocks::ExampleMethods#receive_message_chain
-
-
# @method as_null_object
-
# Tells the object to respond to all messages. If specific stub values
-
# are declared, they'll work as expected. If not, the receiver is
-
# returned.
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
-
# @method null_object?
-
# Returns true if this object has received `as_null_object`
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
end
-
end
-
-
# The legacy `:should` syntax adds the `any_instance` to `Class`.
-
# We generally recommend you use the newer `:expect` syntax instead,
-
# which allows you to stub any instance of a class using
-
# `allow_any_instance_of(klass)` or mock any instance using
-
# `expect_any_instance_of(klass)`.
-
# @see BasicObject
-
1
class Class
-
# @method any_instance
-
# Used to set stubs and message expectations on any instance of a given
-
# class. Returns a [Recorder](Recorder), which records messages like
-
# `stub` and `should_receive` for later playback on instances of the
-
# class.
-
#
-
# @example
-
# Car.any_instance.should_receive(:go)
-
# race = Race.new
-
# race.cars << Car.new
-
# race.go # assuming this delegates to all of its cars
-
# # this example would pass
-
#
-
# Account.any_instance.stub(:balance) { Money.new(:USD, 25) }
-
# Account.new.balance # => Money.new(:USD, 25))
-
#
-
# @return [Recorder]
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
# @see RSpec::Mocks::ExampleMethods#expect_any_instance_of
-
# @see RSpec::Mocks::ExampleMethods#allow_any_instance_of
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class TargetBase
-
1
def initialize(target)
-
@target = target
-
end
-
-
1
def self.delegate_to(matcher_method)
-
4
define_method(:to) do |matcher, &block|
-
unless matcher_allowed?(matcher)
-
raise_unsupported_matcher(:to, matcher)
-
end
-
define_matcher(matcher, matcher_method, &block)
-
end
-
end
-
-
1
def self.delegate_not_to(matcher_method, options={})
-
4
method_name = options.fetch(:from)
-
4
define_method(method_name) do |matcher, &block|
-
case matcher
-
when Matchers::Receive, Matchers::HaveReceived
-
define_matcher(matcher, matcher_method, &block)
-
when Matchers::ReceiveMessages, Matchers::ReceiveMessageChain
-
raise_negation_unsupported(method_name, matcher)
-
else
-
raise_unsupported_matcher(method_name, matcher)
-
end
-
end
-
end
-
-
1
def self.disallow_negation(method_name)
-
4
define_method(method_name) do |matcher, *_args|
-
raise_negation_unsupported(method_name, matcher)
-
end
-
end
-
-
1
private
-
-
1
def matcher_allowed?(matcher)
-
matcher.class.name.start_with?("RSpec::Mocks::Matchers".freeze)
-
end
-
-
1
def define_matcher(matcher, name, &block)
-
matcher.__send__(name, @target, &block)
-
end
-
-
1
def raise_unsupported_matcher(method_name, matcher)
-
raise UnsupportedMatcherError,
-
"only the `receive`, `have_received` and `receive_messages` matchers are supported " \
-
"with `#{expression}(...).#{method_name}`, but you have provided: #{matcher}"
-
end
-
-
1
def raise_negation_unsupported(method_name, matcher)
-
raise NegationUnsupportedError,
-
"`#{expression}(...).#{method_name} #{matcher.name}` is not supported since it " \
-
"doesn't really make sense. What would it even mean?"
-
end
-
-
1
def expression
-
self.class::EXPRESSION
-
end
-
end
-
-
# @private
-
1
class AllowanceTarget < TargetBase
-
1
EXPRESSION = :allow
-
1
delegate_to :setup_allowance
-
1
disallow_negation :not_to
-
1
disallow_negation :to_not
-
end
-
-
# @private
-
1
class ExpectationTarget < TargetBase
-
1
EXPRESSION = :expect
-
1
delegate_to :setup_expectation
-
1
delegate_not_to :setup_negative_expectation, :from => :not_to
-
1
delegate_not_to :setup_negative_expectation, :from => :to_not
-
end
-
-
# @private
-
1
class AnyInstanceAllowanceTarget < TargetBase
-
1
EXPRESSION = :allow_any_instance_of
-
1
delegate_to :setup_any_instance_allowance
-
1
disallow_negation :not_to
-
1
disallow_negation :to_not
-
end
-
-
# @private
-
1
class AnyInstanceExpectationTarget < TargetBase
-
1
EXPRESSION = :expect_any_instance_of
-
1
delegate_to :setup_any_instance_expectation
-
1
delegate_not_to :setup_any_instance_negative_expectation, :from => :not_to
-
1
delegate_not_to :setup_any_instance_negative_expectation, :from => :to_not
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# Implements the methods needed for a pure test double. RSpec::Mocks::Double
-
# includes this module, and it is provided for cases where you want a
-
# pure test double without subclassing RSpec::Mocks::Double.
-
1
module TestDouble
-
# Creates a new test double with a `name` (that will be used in error
-
# messages only)
-
1
def initialize(name=nil, stubs={})
-
@__expired = false
-
if Hash === name && stubs.empty?
-
stubs = name
-
@name = nil
-
else
-
@name = name
-
end
-
assign_stubs(stubs)
-
end
-
-
# Tells the object to respond to all messages. If specific stub values
-
# are declared, they'll work as expected. If not, the receiver is
-
# returned.
-
1
def as_null_object
-
__mock_proxy.as_null_object
-
end
-
-
# Returns true if this object has received `as_null_object`
-
1
def null_object?
-
__mock_proxy.null_object?
-
end
-
-
# This allows for comparing the mock to other objects that proxy such as
-
# ActiveRecords belongs_to proxy objects. By making the other object run
-
# the comparison, we're sure the call gets delegated to the proxy
-
# target.
-
1
def ==(other)
-
other == __mock_proxy
-
end
-
-
# @private
-
1
def inspect
-
TestDoubleFormatter.format(self)
-
end
-
-
# @private
-
1
def to_s
-
inspect.gsub('<', '[').gsub('>', ']')
-
end
-
-
# @private
-
1
def respond_to?(message, incl_private=false)
-
__mock_proxy.null_object? ? true : super
-
end
-
-
# @private
-
1
def __build_mock_proxy_unless_expired(order_group)
-
__raise_expired_error || __build_mock_proxy(order_group)
-
end
-
-
# @private
-
1
def __disallow_further_usage!
-
@__expired = true
-
end
-
-
# Override for default freeze implementation to prevent freezing of test
-
# doubles.
-
1
def freeze
-
RSpec.warn_with("WARNING: you attempted to freeze a test double. This is explicitly a no-op as freezing doubles can lead to undesired behaviour when resetting tests.")
-
end
-
-
1
private
-
-
1
def method_missing(message, *args, &block)
-
proxy = __mock_proxy
-
proxy.record_message_received(message, *args, &block)
-
-
if proxy.null_object?
-
case message
-
when :to_int then return 0
-
when :to_a, :to_ary then return nil
-
when :to_str then return to_s
-
else return self
-
end
-
end
-
-
# Defined private and protected methods will still trigger `method_missing`
-
# when called publicly. We want ruby's method visibility error to get raised,
-
# so we simply delegate to `super` in that case.
-
# ...well, we would delegate to `super`, but there's a JRuby
-
# bug, so we raise our own visibility error instead:
-
# https://github.com/jruby/jruby/issues/1398
-
visibility = proxy.visibility_for(message)
-
if visibility == :private || visibility == :protected
-
ErrorGenerator.new(self).raise_non_public_error(
-
message, visibility
-
)
-
end
-
-
# Required wrapping doubles in an Array on Ruby 1.9.2
-
raise NoMethodError if [:to_a, :to_ary].include? message
-
proxy.raise_unexpected_message_error(message, args)
-
end
-
-
1
def assign_stubs(stubs)
-
stubs.each_pair do |message, response|
-
__mock_proxy.add_simple_stub(message, response)
-
end
-
end
-
-
1
def __mock_proxy
-
::RSpec::Mocks.space.proxy_for(self)
-
end
-
-
1
def __build_mock_proxy(order_group)
-
TestDoubleProxy.new(self, order_group)
-
end
-
-
1
def __raise_expired_error
-
return false unless @__expired
-
ErrorGenerator.new(self).raise_expired_test_double_error
-
end
-
-
1
def initialize_copy(other)
-
as_null_object if other.null_object?
-
super
-
end
-
end
-
-
# A generic test double object. `double`, `instance_double` and friends
-
# return an instance of this.
-
1
class Double
-
1
include TestDouble
-
end
-
-
# @private
-
1
module TestDoubleFormatter
-
1
def self.format(dbl, unwrap=false)
-
format = "#{type_desc(dbl)}#{verified_module_desc(dbl)} #{name_desc(dbl)}"
-
return format if unwrap
-
"#<#{format}>"
-
end
-
-
1
class << self
-
1
private
-
-
1
def type_desc(dbl)
-
case dbl
-
when InstanceVerifyingDouble then "InstanceDouble"
-
when ClassVerifyingDouble then "ClassDouble"
-
when ObjectVerifyingDouble then "ObjectDouble"
-
else "Double"
-
end
-
end
-
-
# @private
-
1
IVAR_GET = Object.instance_method(:instance_variable_get)
-
-
1
def verified_module_desc(dbl)
-
return nil unless VerifyingDouble === dbl
-
"(#{IVAR_GET.bind(dbl).call(:@doubled_module).description})"
-
end
-
-
1
def name_desc(dbl)
-
return "(anonymous)" unless (name = IVAR_GET.bind(dbl).call(:@name))
-
name.inspect
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_mocks 'verifying_proxy'
-
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
module VerifyingDouble
-
1
def respond_to?(message, include_private=false)
-
return super unless null_object?
-
-
method_ref = __mock_proxy.method_reference[message]
-
-
case method_ref.visibility
-
when :public then true
-
when :private then include_private
-
when :protected then include_private || RUBY_VERSION.to_f < 2.0
-
else !method_ref.unimplemented?
-
end
-
end
-
-
1
def method_missing(message, *args, &block)
-
# Null object conditional is an optimization. If not a null object,
-
# validity of method expectations will have been checked at definition
-
# time.
-
if null_object?
-
if @__sending_message == message
-
__mock_proxy.ensure_implemented(message)
-
else
-
__mock_proxy.ensure_publicly_implemented(message, self)
-
end
-
-
__mock_proxy.validate_arguments!(message, args)
-
end
-
-
super
-
end
-
-
# @private
-
1
module SilentIO
-
1
def self.method_missing(*); end
-
1
def self.respond_to?(*)
-
1
true
-
end
-
end
-
-
# Redefining `__send__` causes ruby to issue a warning.
-
1
old, $stderr = $stderr, SilentIO
-
1
def __send__(name, *args, &block)
-
@__sending_message = name
-
super
-
ensure
-
@__sending_message = nil
-
end
-
1
$stderr = old
-
-
1
def send(name, *args, &block)
-
__send__(name, *args, &block)
-
end
-
-
1
def initialize(doubled_module, *args)
-
@doubled_module = doubled_module
-
-
possible_name = args.first
-
name = if String === possible_name || Symbol === possible_name
-
args.shift
-
end
-
-
super(name, *args)
-
@__sending_message = nil
-
end
-
end
-
-
# A mock providing a custom proxy that can verify the validity of any
-
# method stubs or expectations against the public instance methods of the
-
# given class.
-
#
-
# @private
-
1
class InstanceVerifyingDouble
-
1
include TestDouble
-
1
include VerifyingDouble
-
-
1
def __build_mock_proxy(order_group)
-
VerifyingProxy.new(self, order_group,
-
@doubled_module,
-
InstanceMethodReference
-
)
-
end
-
end
-
-
# An awkward module necessary because we cannot otherwise have
-
# ClassVerifyingDouble inherit from Module and still share these methods.
-
#
-
# @private
-
1
module ObjectVerifyingDoubleMethods
-
1
include TestDouble
-
1
include VerifyingDouble
-
-
1
def as_stubbed_const(options={})
-
ConstantMutator.stub(@doubled_module.const_to_replace, self, options)
-
self
-
end
-
-
1
private
-
-
1
def __build_mock_proxy(order_group)
-
VerifyingProxy.new(self, order_group,
-
@doubled_module,
-
ObjectMethodReference
-
)
-
end
-
end
-
-
# Similar to an InstanceVerifyingDouble, except that it verifies against
-
# public methods of the given object.
-
#
-
# @private
-
1
class ObjectVerifyingDouble
-
1
include ObjectVerifyingDoubleMethods
-
end
-
-
# Effectively the same as an ObjectVerifyingDouble (since a class is a type
-
# of object), except with Module in the inheritance chain so that
-
# transferring nested constants to work.
-
#
-
# @private
-
1
class ClassVerifyingDouble < Module
-
1
include ObjectVerifyingDoubleMethods
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support 'method_signature_verifier'
-
-
1
module RSpec
-
1
module Mocks
-
# A message expectation that knows about the real implementation of the
-
# message being expected, so that it can verify that any expectations
-
# have the valid arguments.
-
# @api private
-
1
class VerifyingMessageExpectation < MessageExpectation
-
# A level of indirection is used here rather than just passing in the
-
# method itself, since method look up is expensive and we only want to
-
# do it if actually needed.
-
#
-
# Conceptually the method reference makes more sense as a constructor
-
# argument since it should be immutable, but it is significantly more
-
# straight forward to build the object in pieces so for now it stays as
-
# an accessor.
-
1
attr_accessor :method_reference
-
-
1
def initialize(*args)
-
super
-
end
-
-
# @private
-
1
def with(*args, &block)
-
super(*args, &block).tap do
-
validate_expected_arguments! do |signature|
-
example_call_site_args = [:an_arg] * signature.min_non_kw_args
-
example_call_site_args << :kw_args_hash if signature.required_kw_args.any?
-
@argument_list_matcher.resolve_expected_args_based_on(example_call_site_args)
-
end
-
end
-
end
-
-
1
private
-
-
1
def validate_expected_arguments!
-
return if method_reference.nil?
-
-
method_reference.with_signature do |signature|
-
args = yield signature
-
verifier = Support::LooseSignatureVerifier.new(signature, args)
-
-
unless verifier.valid?
-
# Fail fast is required, otherwise the message expectation will fail
-
# as well ("expected method not called") and clobber this one.
-
@failed_fast = true
-
@error_generator.raise_invalid_arguments_error(verifier)
-
end
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_mocks 'verifying_message_expectation'
-
1
RSpec::Support.require_rspec_mocks 'method_reference'
-
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class CallbackInvocationStrategy
-
1
def call(doubled_module)
-
RSpec::Mocks.configuration.verifying_double_callbacks.each do |block|
-
block.call doubled_module
-
end
-
end
-
end
-
-
# @private
-
1
class NoCallbackInvocationStrategy
-
1
def call(_doubled_module)
-
end
-
end
-
-
# @private
-
1
module VerifyingProxyMethods
-
1
def add_stub(method_name, opts={}, &implementation)
-
ensure_implemented(method_name)
-
super
-
end
-
-
1
def add_simple_stub(method_name, *args)
-
ensure_implemented(method_name)
-
super
-
end
-
-
1
def add_message_expectation(method_name, opts={}, &block)
-
ensure_implemented(method_name)
-
super
-
end
-
-
1
def ensure_implemented(method_name)
-
return unless method_reference[method_name].unimplemented?
-
-
@error_generator.raise_unimplemented_error(
-
@doubled_module,
-
method_name,
-
@object
-
)
-
end
-
-
1
def ensure_publicly_implemented(method_name, _object)
-
ensure_implemented(method_name)
-
visibility = method_reference[method_name].visibility
-
-
return if visibility == :public
-
@error_generator.raise_non_public_error(method_name, visibility)
-
end
-
end
-
-
# A verifying proxy mostly acts like a normal proxy, except that it
-
# contains extra logic to try and determine the validity of any expectation
-
# set on it. This includes whether or not methods have been defined and the
-
# validatiy of arguments on method calls.
-
#
-
# In all other ways this behaves like a normal proxy. It only adds the
-
# verification behaviour to specific methods then delegates to the parent
-
# implementation.
-
#
-
# These checks are only activated if the doubled class has already been
-
# loaded, otherwise they are disabled. This allows for testing in
-
# isolation.
-
#
-
# @private
-
1
class VerifyingProxy < TestDoubleProxy
-
1
include VerifyingProxyMethods
-
-
1
def initialize(object, order_group, doubled_module, method_reference_class)
-
super(object, order_group)
-
@object = object
-
@doubled_module = doubled_module
-
@method_reference_class = method_reference_class
-
-
# A custom method double is required to pass through a way to lookup
-
# methods to determine their parameters. This is only relevant if the doubled
-
# class is loaded.
-
@method_doubles = Hash.new do |h, k|
-
h[k] = VerifyingMethodDouble.new(@object, k, self, method_reference[k])
-
end
-
end
-
-
1
def method_reference
-
@method_reference ||= Hash.new do |h, k|
-
h[k] = @method_reference_class.for(@doubled_module, k)
-
end
-
end
-
-
1
def visibility_for(method_name)
-
method_reference[method_name].visibility
-
end
-
-
1
def validate_arguments!(method_name, args)
-
@method_doubles[method_name].validate_arguments!(args)
-
end
-
end
-
-
# @private
-
1
DEFAULT_CALLBACK_INVOCATION_STRATEGY = CallbackInvocationStrategy.new
-
-
# @private
-
1
class VerifyingPartialDoubleProxy < PartialDoubleProxy
-
1
include VerifyingProxyMethods
-
-
1
def initialize(object, expectation_ordering, optional_callback_invocation_strategy=DEFAULT_CALLBACK_INVOCATION_STRATEGY)
-
super(object, expectation_ordering)
-
@doubled_module = DirectObjectReference.new(object)
-
-
# A custom method double is required to pass through a way to lookup
-
# methods to determine their parameters.
-
@method_doubles = Hash.new do |h, k|
-
h[k] = VerifyingExistingMethodDouble.for(object, k, self)
-
end
-
-
optional_callback_invocation_strategy.call(@doubled_module)
-
end
-
-
1
def method_reference
-
@method_doubles
-
end
-
end
-
-
# @private
-
1
class VerifyingPartialClassDoubleProxy < VerifyingPartialDoubleProxy
-
1
include PartialClassDoubleProxyMethods
-
end
-
-
# @private
-
1
class VerifyingMethodDouble < MethodDouble
-
1
def initialize(object, method_name, proxy, method_reference)
-
super(object, method_name, proxy)
-
@method_reference = method_reference
-
end
-
-
1
def message_expectation_class
-
VerifyingMessageExpectation
-
end
-
-
1
def add_expectation(*args, &block)
-
# explict params necessary for 1.8.7 see #626
-
super(*args, &block).tap { |x| x.method_reference = @method_reference }
-
end
-
-
1
def add_stub(*args, &block)
-
# explict params necessary for 1.8.7 see #626
-
super(*args, &block).tap { |x| x.method_reference = @method_reference }
-
end
-
-
1
def proxy_method_invoked(obj, *args, &block)
-
validate_arguments!(args)
-
super
-
end
-
-
1
def validate_arguments!(actual_args)
-
@method_reference.with_signature do |signature|
-
verifier = Support::StrictSignatureVerifier.new(signature, actual_args)
-
raise ArgumentError, verifier.error_message unless verifier.valid?
-
end
-
end
-
end
-
-
# A VerifyingMethodDouble fetches the method to verify against from the
-
# original object, using a MethodReference. This works for pure doubles,
-
# but when the original object is itself the one being modified we need to
-
# collapse the reference and the method double into a single object so that
-
# we can access the original pristine method definition.
-
#
-
# @private
-
1
class VerifyingExistingMethodDouble < VerifyingMethodDouble
-
1
def initialize(object, method_name, proxy)
-
super(object, method_name, proxy, self)
-
-
@valid_method = object.respond_to?(method_name, true)
-
-
# Trigger an eager find of the original method since if we find it any
-
# later we end up getting a stubbed method with incorrect arity.
-
save_original_implementation_callable!
-
end
-
-
1
def with_signature
-
yield Support::MethodSignature.new(original_implementation_callable)
-
end
-
-
1
def unimplemented?
-
!@valid_method
-
end
-
-
1
def self.for(object, method_name, proxy)
-
if ClassNewMethodReference.applies_to?(method_name) { object }
-
VerifyingExistingClassNewMethodDouble
-
else
-
self
-
end.new(object, method_name, proxy)
-
end
-
end
-
-
# Used in place of a `VerifyingExistingMethodDouble` for the specific case
-
# of mocking or stubbing a `new` method on a class. In this case, we substitute
-
# the method signature from `#initialize` since new's signature is just `*args`.
-
#
-
# @private
-
1
class VerifyingExistingClassNewMethodDouble < VerifyingExistingMethodDouble
-
1
def with_signature
-
yield Support::MethodSignature.new(object.instance_method(:initialize))
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# Version information for RSpec mocks.
-
1
module Version
-
# Version of RSpec mocks currently in use in SemVer format.
-
1
STRING = '3.4.1'
-
end
-
end
-
end
-
1
module RSpec
-
1
module Support
-
# @api private
-
#
-
# Defines a helper method that is optimized to require files from the
-
# named lib. The passed block MUST be `{ |f| require_relative f }`
-
# because for `require_relative` to work properly from within the named
-
# lib the line of code must be IN that lib.
-
#
-
# `require_relative` is preferred when available because it is always O(1),
-
# regardless of the number of dirs in $LOAD_PATH. `require`, on the other
-
# hand, does a linear O(N) search over the dirs in the $LOAD_PATH until
-
# it can resolve the file relative to one of the dirs.
-
1
def self.define_optimized_require_for_rspec(lib, &require_relative)
-
5
name = "require_rspec_#{lib}"
-
-
5
if Kernel.respond_to?(:require_relative)
-
10
(class << self; self; end).__send__(:define_method, name) do |f|
-
101
require_relative.call("#{lib}/#{f}")
-
end
-
else
-
(class << self; self; end).__send__(:define_method, name) do |f|
-
require "rspec/#{lib}/#{f}"
-
end
-
end
-
end
-
-
26
define_optimized_require_for_rspec(:support) { |f| require_relative(f) }
-
1
require_rspec_support "version"
-
1
require_rspec_support "ruby_features"
-
-
# @api private
-
1
KERNEL_METHOD_METHOD = ::Kernel.instance_method(:method)
-
-
# @api private
-
#
-
# Used internally to get a method handle for a particular object
-
# and method name.
-
#
-
# Includes handling for a few special cases:
-
#
-
# - Objects that redefine #method (e.g. an HTTPRequest struct)
-
# - BasicObject subclasses that mixin a Kernel dup (e.g. SimpleDelegator)
-
# - Objects that undefine method and delegate everything to another
-
# object (e.g. Mongoid association objects)
-
1
if RubyFeatures.supports_rebinding_module_methods?
-
1
def self.method_handle_for(object, method_name)
-
KERNEL_METHOD_METHOD.bind(object).call(method_name)
-
rescue NameError => original
-
begin
-
handle = object.method(method_name)
-
raise original unless handle.is_a? Method
-
handle
-
rescue Support::AllExceptionsExceptOnesWeMustNotRescue
-
raise original
-
end
-
end
-
else
-
def self.method_handle_for(object, method_name)
-
if ::Kernel === object
-
KERNEL_METHOD_METHOD.bind(object).call(method_name)
-
else
-
object.method(method_name)
-
end
-
rescue NameError => original
-
begin
-
handle = object.method(method_name)
-
raise original unless handle.is_a? Method
-
handle
-
rescue Support::AllExceptionsExceptOnesWeMustNotRescue
-
raise original
-
end
-
end
-
end
-
-
# A single thread local variable so we don't excessively pollute that namespace.
-
1
def self.thread_local_data
-
57
Thread.current[:__rspec] ||= {}
-
end
-
-
# @api private
-
1
def self.failure_notifier=(callable)
-
thread_local_data[:failure_notifier] = callable
-
end
-
-
# @private
-
1
DEFAULT_FAILURE_NOTIFIER = lambda { |failure, _opts| raise failure }
-
-
# @api private
-
1
def self.failure_notifier
-
thread_local_data[:failure_notifier] || DEFAULT_FAILURE_NOTIFIER
-
end
-
-
# @api private
-
1
def self.notify_failure(failure, options={})
-
failure_notifier.call(failure, options)
-
end
-
-
# @api private
-
1
def self.with_failure_notifier(callable)
-
orig_notifier = failure_notifier
-
self.failure_notifier = callable
-
yield
-
ensure
-
self.failure_notifier = orig_notifier
-
end
-
-
1
class << self
-
# @api private
-
1
attr_writer :warning_notifier
-
end
-
-
# @private
-
1
DEFAULT_WARNING_NOTIFIER = lambda { |warning| ::Kernel.warn warning }
-
-
# @api private
-
1
def self.warning_notifier
-
@warning_notifier ||= DEFAULT_WARNING_NOTIFIER
-
end
-
-
# @private
-
1
module AllExceptionsExceptOnesWeMustNotRescue
-
# These exceptions are dangerous to rescue as rescuing them
-
# would interfere with things we should not interfere with.
-
1
AVOID_RESCUING = [NoMemoryError, SignalException, Interrupt, SystemExit]
-
-
1
def self.===(exception)
-
AVOID_RESCUING.none? { |ar| ar === exception }
-
end
-
end
-
-
# The Differ is only needed when a a spec fails with a diffable failure.
-
# In the more common case of all specs passing or the only failures being
-
# non-diffable, we can avoid the extra cost of loading the differ, diff-lcs,
-
# pp, etc by avoiding an unnecessary require. Instead, autoload will take
-
# care of loading the differ on first use.
-
1
autoload :Differ, "rspec/support/differ"
-
end
-
end
-
1
RSpec::Support.require_rspec_support "ruby_features"
-
-
1
module RSpec
-
# Consistent implementation for "cleaning" the caller method to strip out
-
# non-rspec lines. This enables errors to be reported at the call site in
-
# the code using the library, which is far more useful than the particular
-
# internal method that raised an error.
-
1
class CallerFilter
-
1
RSPEC_LIBS = %w[
-
core
-
mocks
-
expectations
-
support
-
matchers
-
rails
-
]
-
-
1
ADDITIONAL_TOP_LEVEL_FILES = %w[ autorun ]
-
-
1
LIB_REGEX = %r{/lib/rspec/(#{(RSPEC_LIBS + ADDITIONAL_TOP_LEVEL_FILES).join('|')})(\.rb|/)}
-
-
# rubygems/core_ext/kernel_require.rb isn't actually part of rspec (obviously) but we want
-
# it ignored when we are looking for the first meaningful line of the backtrace outside
-
# of RSpec. It can show up in the backtrace as the immediate first caller
-
# when `CallerFilter.first_non_rspec_line` is called from the top level of a required
-
# file, but it depends on if rubygems is loaded or not. We don't want to have to deal
-
# with this complexity in our `RSpec.deprecate` calls, so we ignore it here.
-
1
IGNORE_REGEX = Regexp.union(LIB_REGEX, "rubygems/core_ext/kernel_require.rb")
-
-
1
if RSpec::Support::RubyFeatures.caller_locations_supported?
-
# This supports args because it's more efficient when the caller specifies
-
# these. It allows us to skip frames the caller knows are part of RSpec,
-
# and to decrease the increment size if the caller is confident the line will
-
# be found in a small number of stack frames from `skip_frames`.
-
#
-
# Note that there is a risk to passing a `skip_frames` value that is too high:
-
# If it skippped the first non-rspec line, then this method would return the
-
# 2nd or 3rd (or whatever) non-rspec line. Thus, you generally shouldn't pass
-
# values for these parameters, particularly since most places that use this are
-
# not hot spots (generally it gets used for deprecation warnings). However,
-
# if you do have a hot spot that calls this, passing `skip_frames` can make
-
# a significant difference. Just make sure that that particular use is tested
-
# so that if the provided `skip_frames` changes to no longer be accurate in
-
# such a way that would return the wrong stack frame, a test will fail to tell you.
-
#
-
# See benchmarks/skip_frames_for_caller_filter.rb for measurements.
-
1
def self.first_non_rspec_line(skip_frames=3, increment=5)
-
# Why a default `skip_frames` of 3?
-
# By the time `caller_locations` is called below, the first 3 frames are:
-
# lib/rspec/support/caller_filter.rb:63:in `block in first_non_rspec_line'
-
# lib/rspec/support/caller_filter.rb:62:in `loop'
-
# lib/rspec/support/caller_filter.rb:62:in `first_non_rspec_line'
-
-
# `caller` is an expensive method that scales linearly with the size of
-
# the stack. The performance hit for fetching it in chunks is small,
-
# and since the target line is probably near the top of the stack, the
-
# overall improvement of a chunked search like this is significant.
-
#
-
# See benchmarks/caller.rb for measurements.
-
-
# The default increment of 5 for this method are mostly arbitrary, but
-
# is chosen to give good performance on the common case of creating a double.
-
-
loop do
-
stack = caller_locations(skip_frames, increment)
-
raise "No non-lib lines in stack" unless stack
-
-
line = stack.find { |l| l.path !~ IGNORE_REGEX }
-
return line.to_s if line
-
-
skip_frames += increment
-
increment *= 2 # The choice of two here is arbitrary.
-
end
-
end
-
else
-
# Earlier rubies do not support the two argument form of `caller`. This
-
# fallback is logically the same, but slower.
-
def self.first_non_rspec_line(*)
-
caller.find { |line| line !~ IGNORE_REGEX }
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Support
-
# @private
-
1
class ComparableVersion
-
1
include Comparable
-
-
1
attr_reader :string
-
-
1
def initialize(string)
-
2
@string = string
-
end
-
-
1
def <=>(other)
-
1
other = self.class.new(other) unless other.is_a?(self.class)
-
-
1
return 0 if string == other.string
-
-
3
longer_segment_count = [self, other].map { |version| version.segments.count }.max
-
-
1
longer_segment_count.times do |index|
-
1
self_segment = segments[index] || 0
-
1
other_segment = other.segments[index] || 0
-
-
1
if self_segment.class == other_segment.class
-
1
result = self_segment <=> other_segment
-
1
return result unless result == 0
-
else
-
return self_segment.is_a?(String) ? -1 : 1
-
end
-
end
-
-
0
-
end
-
-
1
def segments
-
@segments ||= string.scan(/[a-z]+|\d+/i).map do |segment|
-
6
if segment =~ /\A\d+\z/
-
6
segment.to_i
-
else
-
segment
-
end
-
4
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support 'ruby_features'
-
-
1
module RSpec
-
1
module Support
-
# @api private
-
#
-
# Replacement for fileutils#mkdir_p because we don't want to require parts
-
# of stdlib in RSpec.
-
1
class DirectoryMaker
-
# @api private
-
#
-
# Implements nested directory construction
-
1
def self.mkdir_p(path)
-
stack = generate_stack(path)
-
path.split(File::SEPARATOR).each do |part|
-
stack = generate_path(stack, part)
-
begin
-
Dir.mkdir(stack) unless directory_exists?(stack)
-
rescue Errno::EEXIST => e
-
raise e unless directory_exists?(stack)
-
rescue Errno::ENOTDIR => e
-
raise Errno::EEXIST, e.message
-
end
-
end
-
end
-
-
1
if OS.windows_file_path?
-
def self.generate_stack(path)
-
if path.start_with?(File::SEPARATOR)
-
File::SEPARATOR
-
elsif path[1] == ':'
-
''
-
else
-
'.'
-
end
-
end
-
def self.generate_path(stack, part)
-
if stack == ''
-
part
-
elsif stack == File::SEPARATOR
-
File.join('', part)
-
else
-
File.join(stack, part)
-
end
-
end
-
else
-
1
def self.generate_stack(path)
-
path.start_with?(File::SEPARATOR) ? File::SEPARATOR : "."
-
end
-
1
def self.generate_path(stack, part)
-
File.join(stack, part)
-
end
-
end
-
-
1
def self.directory_exists?(dirname)
-
File.exist?(dirname) && File.directory?(dirname)
-
end
-
1
private_class_method :directory_exists?
-
1
private_class_method :generate_stack
-
1
private_class_method :generate_path
-
end
-
end
-
end
-
1
module RSpec
-
1
module Support
-
# @private
-
1
class EncodedString
-
# Reduce allocations by storing constants.
-
1
UTF_8 = "UTF-8"
-
1
US_ASCII = "US-ASCII"
-
#
-
# In MRI 2.1 'invalid: :replace' changed to also replace an invalid byte sequence
-
# see https://github.com/ruby/ruby/blob/v2_1_0/NEWS#L176
-
# https://www.ruby-forum.com/topic/6861247
-
# https://twitter.com/nalsh/status/553413844685438976
-
#
-
# For example, given:
-
# "\x80".force_encoding("Emacs-Mule").encode(:invalid => :replace).bytes.to_a
-
#
-
# On MRI 2.1 or above: 63 # '?'
-
# else : 128 # "\x80"
-
#
-
# Ruby's default replacement string is:
-
# U+FFFD ("\xEF\xBF\xBD"), for Unicode encoding forms, else
-
# ? ("\x3F")
-
1
REPLACE = "?"
-
1
ENCODE_UNCONVERTABLE_BYTES = {
-
:invalid => :replace,
-
:undef => :replace,
-
:replace => REPLACE
-
}
-
1
ENCODE_NO_CONVERTER = {
-
:invalid => :replace,
-
:replace => REPLACE
-
}
-
-
1
def initialize(string, encoding=nil)
-
@encoding = encoding
-
@source_encoding = detect_source_encoding(string)
-
@string = matching_encoding(string)
-
end
-
1
attr_reader :source_encoding
-
-
1
delegated_methods = String.instance_methods.map(&:to_s) & %w[eql? lines == encoding empty?]
-
1
delegated_methods.each do |name|
-
5
define_method(name) { |*args, &block| @string.__send__(name, *args, &block) }
-
end
-
-
1
def <<(string)
-
@string << matching_encoding(string)
-
end
-
-
1
def split(regex_or_string)
-
@string.split(matching_encoding(regex_or_string))
-
end
-
-
1
def to_s
-
@string
-
end
-
1
alias :to_str :to_s
-
-
1
if String.method_defined?(:encoding)
-
-
1
private
-
-
# Encoding Exceptions:
-
#
-
# Raised by Encoding and String methods:
-
# Encoding::UndefinedConversionError:
-
# when a transcoding operation fails
-
# if the String contains characters invalid for the target encoding
-
# e.g. "\x80".encode('UTF-8','ASCII-8BIT')
-
# vs "\x80".encode('UTF-8','ASCII-8BIT', undef: :replace, replace: '<undef>')
-
# # => '<undef>'
-
# Encoding::CompatibilityError
-
# when Encoding.compatibile?(str1, str2) is nil
-
# e.g. utf_16le_emoji_string.split("\n")
-
# e.g. valid_unicode_string.encode(utf8_encoding) << ascii_string
-
# Encoding::InvalidByteSequenceError:
-
# when the string being transcoded contains a byte invalid for
-
# either the source or target encoding
-
# e.g. "\x80".encode('UTF-8','US-ASCII')
-
# vs "\x80".encode('UTF-8','US-ASCII', invalid: :replace, replace: '<byte>')
-
# # => '<byte>'
-
# ArgumentError
-
# when operating on a string with invalid bytes
-
# e.g."\x80".split("\n")
-
# TypeError
-
# when a symbol is passed as an encoding
-
# Encoding.find(:"UTF-8")
-
# when calling force_encoding on an object
-
# that doesn't respond to #to_str
-
#
-
# Raised by transcoding methods:
-
# Encoding::ConverterNotFoundError:
-
# when a named encoding does not correspond with a known converter
-
# e.g. 'abc'.force_encoding('UTF-8').encode('foo')
-
# or a converter path cannot be found
-
# e.g. "\x80".force_encoding('ASCII-8BIT').encode('Emacs-Mule')
-
#
-
# Raised by byte <-> char conversions
-
# RangeError: out of char range
-
# e.g. the UTF-16LE emoji: 128169.chr
-
1
def matching_encoding(string)
-
string = remove_invalid_bytes(string)
-
string.encode(@encoding)
-
rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError
-
string.encode(@encoding, ENCODE_UNCONVERTABLE_BYTES)
-
rescue Encoding::ConverterNotFoundError
-
string.dup.force_encoding(@encoding).encode(ENCODE_NO_CONVERTER)
-
end
-
-
# Prevents raising ArgumentError
-
1
if String.method_defined?(:scrub)
-
# https://github.com/ruby/ruby/blob/eeb05e8c11/doc/NEWS-2.1.0#L120-L123
-
# https://github.com/ruby/ruby/blob/v2_1_0/string.c#L8242
-
# https://github.com/hsbt/string-scrub
-
# https://github.com/rubinius/rubinius/blob/v2.5.2/kernel/common/string.rb#L1913-L1972
-
1
def remove_invalid_bytes(string)
-
string.scrub(REPLACE)
-
end
-
else
-
# http://stackoverflow.com/a/8711118/879854
-
# Loop over chars in a string replacing chars
-
# with invalid encoding, which is a pretty good proxy
-
# for the invalid byte sequence that causes an ArgumentError
-
def remove_invalid_bytes(string)
-
string.chars.map do |char|
-
char.valid_encoding? ? char : REPLACE
-
end.join
-
end
-
end
-
-
1
def detect_source_encoding(string)
-
string.encoding
-
end
-
-
1
def self.pick_encoding(source_a, source_b)
-
Encoding.compatible?(source_a, source_b) || Encoding.default_external
-
end
-
else
-
-
def self.pick_encoding(_source_a, _source_b)
-
end
-
-
private
-
-
def matching_encoding(string)
-
string
-
end
-
-
def detect_source_encoding(_string)
-
US_ASCII
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Support
-
# Provides a means to fuzzy-match between two arbitrary objects.
-
# Understands array/hash nesting. Uses `===` or `==` to
-
# perform the matching.
-
1
module FuzzyMatcher
-
# @api private
-
1
def self.values_match?(expected, actual)
-
5
if Hash === actual
-
return hashes_match?(expected, actual) if Hash === expected
-
elsif Array === expected && Enumerable === actual && !(Struct === actual)
-
return arrays_match?(expected, actual.to_a)
-
end
-
-
5
return true if expected == actual
-
-
4
begin
-
4
expected === actual
-
rescue ArgumentError
-
# Some objects, like 0-arg lambdas on 1.9+, raise
-
# ArgumentError for `expected === actual`.
-
false
-
end
-
end
-
-
# @private
-
1
def self.arrays_match?(expected_list, actual_list)
-
return false if expected_list.size != actual_list.size
-
-
expected_list.zip(actual_list).all? do |expected, actual|
-
values_match?(expected, actual)
-
end
-
end
-
-
# @private
-
1
def self.hashes_match?(expected_hash, actual_hash)
-
return false if expected_hash.size != actual_hash.size
-
-
expected_hash.all? do |expected_key, expected_value|
-
actual_value = actual_hash.fetch(expected_key) { return false }
-
values_match?(expected_value, actual_value)
-
end
-
end
-
-
1
private_class_method :arrays_match?, :hashes_match?
-
end
-
end
-
end
-
1
module RSpec
-
1
module Support
-
# @private
-
1
def self.matcher_definitions
-
2
@matcher_definitions ||= []
-
end
-
-
# Used internally to break cyclic dependency between mocks, expectations,
-
# and support. We don't currently have a consistent implementation of our
-
# matchers, though we are considering changing that:
-
# https://github.com/rspec/rspec-mocks/issues/513
-
#
-
# @private
-
1
def self.register_matcher_definition(&block)
-
2
matcher_definitions << block
-
end
-
-
# Remove a previously registered matcher. Useful for cleaning up after
-
# yourself in specs.
-
#
-
# @private
-
1
def self.deregister_matcher_definition(&block)
-
matcher_definitions.delete(block)
-
end
-
-
# @private
-
1
def self.is_a_matcher?(object)
-
matcher_definitions.any? { |md| md.call(object) }
-
end
-
-
# @api private
-
#
-
# gives a string representation of an object for use in RSpec descriptions
-
1
def self.rspec_description_for_object(object)
-
if RSpec::Support.is_a_matcher?(object) && object.respond_to?(:description)
-
object.description
-
else
-
object
-
end
-
end
-
end
-
end
-
1
require 'rspec/support'
-
1
RSpec::Support.require_rspec_support "ruby_features"
-
1
RSpec::Support.require_rspec_support "matcher_definition"
-
-
1
module RSpec
-
1
module Support
-
# Extracts info about the number of arguments and allowed/required
-
# keyword args of a given method.
-
#
-
# @private
-
1
class MethodSignature
-
1
attr_reader :min_non_kw_args, :max_non_kw_args, :optional_kw_args, :required_kw_args
-
-
1
def initialize(method)
-
@method = method
-
@optional_kw_args = []
-
@required_kw_args = []
-
classify_parameters
-
end
-
-
1
def non_kw_args_arity_description
-
case max_non_kw_args
-
when min_non_kw_args then min_non_kw_args.to_s
-
when INFINITY then "#{min_non_kw_args} or more"
-
else "#{min_non_kw_args} to #{max_non_kw_args}"
-
end
-
end
-
-
1
def valid_non_kw_args?(positional_arg_count)
-
min_non_kw_args <= positional_arg_count &&
-
positional_arg_count <= max_non_kw_args
-
end
-
-
1
if RubyFeatures.optional_and_splat_args_supported?
-
1
def description
-
@description ||= begin
-
parts = []
-
-
unless non_kw_args_arity_description == "0"
-
parts << "arity of #{non_kw_args_arity_description}"
-
end
-
-
if @optional_kw_args.any?
-
parts << "optional keyword args (#{@optional_kw_args.map(&:inspect).join(", ")})"
-
end
-
-
if @required_kw_args.any?
-
parts << "required keyword args (#{@required_kw_args.map(&:inspect).join(", ")})"
-
end
-
-
parts << "any additional keyword args" if @allows_any_kw_args
-
-
parts.join(" and ")
-
end
-
end
-
-
1
def missing_kw_args_from(given_kw_args)
-
@required_kw_args - given_kw_args
-
end
-
-
1
def invalid_kw_args_from(given_kw_args)
-
return [] if @allows_any_kw_args
-
given_kw_args - @allowed_kw_args
-
end
-
-
1
def has_kw_args_in?(args)
-
Hash === args.last && could_contain_kw_args?(args)
-
end
-
-
# Without considering what the last arg is, could it
-
# contain keyword arguments?
-
1
def could_contain_kw_args?(args)
-
return false if args.count <= min_non_kw_args
-
@allows_any_kw_args || @allowed_kw_args.any?
-
end
-
-
1
def classify_parameters
-
optional_non_kw_args = @min_non_kw_args = 0
-
@allows_any_kw_args = false
-
-
@method.parameters.each do |(type, name)|
-
case type
-
# def foo(a:)
-
when :keyreq then @required_kw_args << name
-
# def foo(a: 1)
-
when :key then @optional_kw_args << name
-
# def foo(**kw_args)
-
when :keyrest then @allows_any_kw_args = true
-
# def foo(a)
-
when :req then @min_non_kw_args += 1
-
# def foo(a = 1)
-
when :opt then optional_non_kw_args += 1
-
# def foo(*a)
-
when :rest then optional_non_kw_args = INFINITY
-
end
-
end
-
-
@max_non_kw_args = @min_non_kw_args + optional_non_kw_args
-
@allowed_kw_args = @required_kw_args + @optional_kw_args
-
end
-
else
-
def description
-
"arity of #{non_kw_args_arity_description}"
-
end
-
-
def missing_kw_args_from(_given_kw_args)
-
[]
-
end
-
-
def invalid_kw_args_from(_given_kw_args)
-
[]
-
end
-
-
def has_kw_args_in?(_args)
-
false
-
end
-
-
def could_contain_kw_args?(*)
-
false
-
end
-
-
def classify_parameters
-
arity = @method.arity
-
if arity < 0
-
# `~` inverts the one's complement and gives us the
-
# number of required args
-
@min_non_kw_args = ~arity
-
@max_non_kw_args = INFINITY
-
else
-
@min_non_kw_args = arity
-
@max_non_kw_args = arity
-
end
-
end
-
end
-
-
1
INFINITY = 1 / 0.0
-
end
-
-
# Some versions of JRuby have a nasty bug we have to work around :(.
-
# https://github.com/jruby/jruby/issues/2816
-
if RSpec::Support::Ruby.jruby? &&
-
1
RubyFeatures.optional_and_splat_args_supported? &&
-
Class.new { attr_writer :foo }.instance_method(:foo=).parameters == []
-
-
class MethodSignature < remove_const(:MethodSignature)
-
private
-
-
def classify_parameters
-
super
-
return unless @method.parameters == [] && @method.arity == 1
-
@max_non_kw_args = @min_non_kw_args = 1
-
end
-
end
-
end
-
-
# Deals with the slightly different semantics of block arguments.
-
# For methods, arguments are required unless a default value is provided.
-
# For blocks, arguments are optional, even if no default value is provided.
-
#
-
# However, we want to treat block args as required since you virtually
-
# always want to pass a value for each received argument and our
-
# `and_yield` has treated block args as required for many years.
-
#
-
# @api private
-
1
class BlockSignature < MethodSignature
-
1
if RubyFeatures.optional_and_splat_args_supported?
-
1
def classify_parameters
-
super
-
@min_non_kw_args = @max_non_kw_args unless @max_non_kw_args == INFINITY
-
end
-
end
-
end
-
-
# Abstract base class for signature verifiers.
-
#
-
# @api private
-
1
class MethodSignatureVerifier
-
1
attr_reader :non_kw_args, :kw_args
-
-
1
def initialize(signature, args)
-
@signature = signature
-
@non_kw_args, @kw_args = split_args(*args)
-
end
-
-
1
def valid?
-
missing_kw_args.empty? &&
-
invalid_kw_args.empty? &&
-
valid_non_kw_args?
-
end
-
-
1
def error_message
-
if missing_kw_args.any?
-
"Missing required keyword arguments: %s" % [
-
missing_kw_args.join(", ")
-
]
-
elsif invalid_kw_args.any?
-
"Invalid keyword arguments provided: %s" % [
-
invalid_kw_args.join(", ")
-
]
-
elsif !valid_non_kw_args?
-
"Wrong number of arguments. Expected %s, got %s." % [
-
@signature.non_kw_args_arity_description,
-
non_kw_args.length
-
]
-
end
-
end
-
-
1
private
-
-
1
def valid_non_kw_args?
-
@signature.valid_non_kw_args?(non_kw_args.length)
-
end
-
-
1
def missing_kw_args
-
@signature.missing_kw_args_from(kw_args)
-
end
-
-
1
def invalid_kw_args
-
@signature.invalid_kw_args_from(kw_args)
-
end
-
-
1
def split_args(*args)
-
kw_args = if @signature.has_kw_args_in?(args)
-
args.pop.keys
-
else
-
[]
-
end
-
-
[args, kw_args]
-
end
-
end
-
-
# Figures out wether a given method can accept various arguments.
-
# Surprisingly non-trivial.
-
#
-
# @private
-
1
StrictSignatureVerifier = MethodSignatureVerifier
-
-
# Allows matchers to be used instead of providing keyword arguments. In
-
# practice, when this happens only the arity of the method is verified.
-
#
-
# @private
-
1
class LooseSignatureVerifier < MethodSignatureVerifier
-
1
private
-
-
1
def split_args(*args)
-
if RSpec::Support.is_a_matcher?(args.last) && @signature.could_contain_kw_args?(args)
-
args.pop
-
@signature = SignatureWithKeywordArgumentsMatcher.new(@signature)
-
end
-
-
super(*args)
-
end
-
-
# If a matcher is used in a signature in place of keyword arguments, all
-
# keyword argument validation needs to be skipped since the matcher is
-
# opaque.
-
#
-
# Instead, keyword arguments will be validated when the method is called
-
# and they are actually known.
-
#
-
# @private
-
1
class SignatureWithKeywordArgumentsMatcher
-
1
def initialize(signature)
-
@signature = signature
-
end
-
-
1
def missing_kw_args_from(_kw_args)
-
[]
-
end
-
-
1
def invalid_kw_args_from(_kw_args)
-
[]
-
end
-
-
1
def non_kw_args_arity_description
-
@signature.non_kw_args_arity_description
-
end
-
-
1
def valid_non_kw_args?(*args)
-
@signature.valid_non_kw_args?(*args)
-
end
-
-
1
def has_kw_args_in?(args)
-
@signature.has_kw_args_in?(args)
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Support
-
# Provide additional output details beyond what `inspect` provides when
-
# printing Time, DateTime, or BigDecimal
-
1
module ObjectFormatter
-
# @api private
-
1
def self.format(object)
-
prepare_for_inspection(object).inspect
-
end
-
-
# rubocop:disable MethodLength
-
-
# @private
-
# Prepares the provided object to be formatted by wrapping it as needed
-
# in something that, when `inspect` is called on it, will produce the
-
# desired output.
-
#
-
# This allows us to apply the desired formatting to hash/array data structures
-
# at any level of nesting, simply by walking that structure and replacing items
-
# with custom items that have `inspect` defined to return the desired output
-
# for that item. Then we can just use `Array#inspect` or `Hash#inspect` to
-
# format the entire thing.
-
1
def self.prepare_for_inspection(object)
-
case object
-
when Array
-
return object.map { |o| prepare_for_inspection(o) }
-
when Hash
-
return prepare_hash(object)
-
when Time
-
inspection = format_time(object)
-
else
-
if defined?(DateTime) && DateTime === object
-
inspection = format_date_time(object)
-
elsif defined?(BigDecimal) && BigDecimal === object
-
inspection = "#{object.to_s 'F'} (#{object.inspect})"
-
elsif RSpec::Support.is_a_matcher?(object) && object.respond_to?(:description)
-
inspection = object.description
-
else
-
return DelegatingInspector.new(object)
-
end
-
end
-
-
InspectableItem.new(inspection)
-
end
-
# rubocop:enable MethodLength
-
-
# @private
-
1
def self.prepare_hash(input)
-
input.inject({}) do |hash, (k, v)|
-
hash[prepare_for_inspection(k)] = prepare_for_inspection(v)
-
hash
-
end
-
end
-
-
1
TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
-
-
1
if Time.method_defined?(:nsec)
-
# @private
-
1
def self.format_time(time)
-
time.strftime("#{TIME_FORMAT}.#{"%09d" % time.nsec} %z")
-
end
-
else # for 1.8.7
-
# @private
-
def self.format_time(time)
-
time.strftime("#{TIME_FORMAT}.#{"%06d" % time.usec} %z")
-
end
-
end
-
-
1
DATE_TIME_FORMAT = "%a, %d %b %Y %H:%M:%S.%N %z"
-
# ActiveSupport sometimes overrides inspect. If `ActiveSupport` is
-
# defined use a custom format string that includes more time precision.
-
# @private
-
1
def self.format_date_time(date_time)
-
if defined?(ActiveSupport)
-
date_time.strftime(DATE_TIME_FORMAT)
-
else
-
date_time.inspect
-
end
-
end
-
-
# @private
-
1
InspectableItem = Struct.new(:inspection) do
-
1
def inspect
-
inspection
-
end
-
-
1
def pretty_print(pp)
-
pp.text inspection
-
end
-
end
-
-
# @private
-
1
DelegatingInspector = Struct.new(:object) do
-
1
def inspect
-
if defined?(::Delegator) && ::Delegator === object
-
"#<#{object.class}(#{ObjectFormatter.format(object.__getobj__)})>"
-
else
-
object.inspect
-
end
-
end
-
-
1
def pretty_print(pp)
-
pp.text inspect
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Support
-
# Provides recursive constant lookup methods useful for
-
# constant stubbing.
-
1
module RecursiveConstMethods
-
# We only want to consider constants that are defined directly on a
-
# particular module, and not include top-level/inherited constants.
-
# Unfortunately, the constant API changed between 1.8 and 1.9, so
-
# we need to conditionally define methods to ignore the top-level/inherited
-
# constants.
-
#
-
# Given:
-
# class A; B = 1; end
-
# class C < A; end
-
#
-
# On 1.8:
-
# - C.const_get("Hash") # => ::Hash
-
# - C.const_defined?("Hash") # => false
-
# - C.constants # => ["B"]
-
# - None of these methods accept the extra `inherit` argument
-
# On 1.9:
-
# - C.const_get("Hash") # => ::Hash
-
# - C.const_defined?("Hash") # => true
-
# - C.const_get("Hash", false) # => raises NameError
-
# - C.const_defined?("Hash", false) # => false
-
# - C.constants # => [:B]
-
# - C.constants(false) #=> []
-
1
if Module.method(:const_defined?).arity == 1
-
def const_defined_on?(mod, const_name)
-
mod.const_defined?(const_name)
-
end
-
-
def get_const_defined_on(mod, const_name)
-
return mod.const_get(const_name) if const_defined_on?(mod, const_name)
-
-
raise NameError, "uninitialized constant #{mod.name}::#{const_name}"
-
end
-
-
def constants_defined_on(mod)
-
mod.constants.select { |c| const_defined_on?(mod, c) }
-
end
-
else
-
1
def const_defined_on?(mod, const_name)
-
11
mod.const_defined?(const_name, false)
-
end
-
-
1
def get_const_defined_on(mod, const_name)
-
mod.const_get(const_name, false)
-
end
-
-
1
def constants_defined_on(mod)
-
mod.constants(false)
-
end
-
end
-
-
1
def recursive_const_get(const_name)
-
normalize_const_name(const_name).split('::').inject(Object) do |mod, name|
-
get_const_defined_on(mod, name)
-
end
-
end
-
-
1
def recursive_const_defined?(const_name)
-
parts = normalize_const_name(const_name).split('::')
-
parts.inject([Object, '']) do |(mod, full_name), name|
-
yield(full_name, name) if block_given? && !(Module === mod)
-
return false unless const_defined_on?(mod, name)
-
[get_const_defined_on(mod, name), [mod, name].join('::')]
-
end
-
end
-
-
1
def normalize_const_name(const_name)
-
const_name.sub(/\A::/, '')
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Support
-
# Allows a thread to lock out other threads from a critical section of code,
-
# while allowing the thread with the lock to reenter that section.
-
#
-
# Based on Monitor as of 2.2 -
-
# https://github.com/ruby/ruby/blob/eb7ddaa3a47bf48045d26c72eb0f263a53524ebc/lib/monitor.rb#L9
-
#
-
# Depends on Mutex, but Mutex is only available as part of core since 1.9.1:
-
# exists - http://ruby-doc.org/core-1.9.1/Mutex.html
-
# dne - http://ruby-doc.org/core-1.9.0/Mutex.html
-
#
-
# @private
-
1
class ReentrantMutex
-
1
def initialize
-
90
@owner = nil
-
90
@count = 0
-
90
@mutex = Mutex.new
-
end
-
-
1
def synchronize
-
enter
-
yield
-
ensure
-
exit
-
end
-
-
1
private
-
-
1
def enter
-
@mutex.lock if @owner != Thread.current
-
@owner = Thread.current
-
@count += 1
-
end
-
-
1
def exit
-
@count -= 1
-
return unless @count == 0
-
@owner = nil
-
@mutex.unlock
-
end
-
end
-
-
1
if defined? ::Mutex
-
# On 1.9 and up, this is in core, so we just use the real one
-
1
Mutex = ::Mutex
-
else # For 1.8.7
-
# :nocov:
-
skipped
RSpec::Support.require_rspec_support "mutex"
-
# :nocov:
-
end
-
end
-
end
-
1
require 'rbconfig'
-
1
RSpec::Support.require_rspec_support "comparable_version"
-
-
1
module RSpec
-
1
module Support
-
# @api private
-
#
-
# Provides query methods for different OS or OS features.
-
1
module OS
-
1
module_function
-
-
1
def windows?
-
3
!!(RbConfig::CONFIG['host_os'] =~ /cygwin|mswin|mingw|bccwin|wince|emx/)
-
end
-
-
1
def windows_file_path?
-
1
::File::ALT_SEPARATOR == '\\'
-
end
-
end
-
-
# @api private
-
#
-
# Provides query methods for different rubies
-
1
module Ruby
-
1
module_function
-
-
1
def jruby?
-
4
RUBY_PLATFORM == 'java'
-
end
-
-
1
def rbx?
-
1
defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
-
end
-
-
1
def non_mri?
-
!mri?
-
end
-
-
1
def mri?
-
2
!defined?(RUBY_ENGINE) || RUBY_ENGINE == 'ruby'
-
end
-
end
-
-
# @api private
-
#
-
# Provides query methods for ruby features that differ among
-
# implementations.
-
1
module RubyFeatures
-
1
module_function
-
-
1
def optional_and_splat_args_supported?
-
2
Method.method_defined?(:parameters)
-
end
-
-
1
def caller_locations_supported?
-
1
respond_to?(:caller_locations, true)
-
end
-
-
1
if Exception.method_defined?(:cause)
-
1
def supports_exception_cause?
-
1
true
-
end
-
else
-
def supports_exception_cause?
-
false
-
end
-
end
-
-
1
ripper_requirements = [ComparableVersion.new(RUBY_VERSION) >= '1.9.2']
-
-
1
ripper_requirements.push(false) if Ruby.rbx?
-
-
1
if Ruby.jruby?
-
ripper_requirements.push(ComparableVersion.new(JRUBY_VERSION) >= '1.7.5')
-
# Ripper on JRuby 9.0.0.0.rc1 or later reports wrong line number.
-
ripper_requirements.push(ComparableVersion.new(JRUBY_VERSION) < '9.0.0.0.rc1')
-
end
-
-
1
if ripper_requirements.all?
-
1
def ripper_supported?
-
1
true
-
end
-
else
-
def ripper_supported?
-
false
-
end
-
end
-
-
1
if Ruby.mri?
-
1
def kw_args_supported?
-
RUBY_VERSION >= '2.0.0'
-
end
-
-
1
def required_kw_args_supported?
-
RUBY_VERSION >= '2.1.0'
-
end
-
-
1
def supports_rebinding_module_methods?
-
1
RUBY_VERSION.to_i >= 2
-
end
-
else
-
# RBX / JRuby et al support is unknown for keyword arguments
-
# rubocop:disable Lint/Eval
-
begin
-
eval("o = Object.new; def o.m(a: 1); end;"\
-
" raise SyntaxError unless o.method(:m).parameters.include?([:key, :a])")
-
-
def kw_args_supported?
-
true
-
end
-
rescue SyntaxError
-
def kw_args_supported?
-
false
-
end
-
end
-
-
begin
-
eval("o = Object.new; def o.m(a: ); end;"\
-
"raise SyntaxError unless o.method(:m).parameters.include?([:keyreq, :a])")
-
-
def required_kw_args_supported?
-
true
-
end
-
rescue SyntaxError
-
def required_kw_args_supported?
-
false
-
end
-
end
-
-
begin
-
Module.new { def foo; end }.instance_method(:foo).bind(Object.new)
-
-
def supports_rebinding_module_methods?
-
true
-
end
-
rescue TypeError
-
def supports_rebinding_module_methods?
-
false
-
end
-
end
-
# rubocop:enable Lint/Eval
-
end
-
-
1
def module_prepends_supported?
-
4
Module.method_defined?(:prepend) || Module.private_method_defined?(:prepend)
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Support
-
1
module Version
-
1
STRING = '3.4.1'
-
end
-
end
-
end
-
1
require 'rspec/support'
-
1
RSpec::Support.require_rspec_support "caller_filter"
-
-
1
module RSpec
-
1
module Support
-
1
module Warnings
-
1
def deprecate(deprecated, options={})
-
warn_with "DEPRECATION: #{deprecated} is deprecated.", options
-
end
-
-
# @private
-
#
-
# Used internally to print deprecation warnings
-
# when rspec-core isn't loaded
-
1
def warn_deprecation(message, options={})
-
warn_with "DEPRECATION: \n #{message}", options
-
end
-
-
# @private
-
#
-
# Used internally to print warnings
-
1
def warning(text, options={})
-
warn_with "WARNING: #{text}.", options
-
end
-
-
# @private
-
#
-
# Used internally to print longer warnings
-
1
def warn_with(message, options={})
-
call_site = options.fetch(:call_site) { CallerFilter.first_non_rspec_line }
-
message << " Use #{options[:replacement]} instead." if options[:replacement]
-
message << " Called from #{call_site}." if call_site
-
Support.warning_notifier.call message
-
end
-
end
-
end
-
-
1
extend RSpec::Support::Warnings
-
end
-
1
require 'vcr/util/logger'
-
1
require 'vcr/util/variable_args_block_caller'
-
-
1
require 'vcr/cassette'
-
1
require 'vcr/cassette/serializers'
-
1
require 'vcr/cassette/persisters'
-
1
require 'vcr/linked_cassette'
-
1
require 'vcr/configuration'
-
1
require 'vcr/deprecations'
-
1
require 'vcr/errors'
-
1
require 'vcr/library_hooks'
-
1
require 'vcr/request_ignorer'
-
1
require 'vcr/request_matcher_registry'
-
1
require 'vcr/structs'
-
1
require 'vcr/version'
-
-
# The main entry point for VCR.
-
# @note This module is extended onto itself; thus, the methods listed
-
# here as instance methods are available directly off of VCR.
-
1
module VCR
-
1
include VariableArgsBlockCaller
-
1
include Errors
-
-
1
extend self
-
-
# Mutex to synchronize access to cassettes in a threaded environment
-
1
CassetteMutex = Mutex.new
-
-
# The main thread in which VCR was loaded
-
1
MainThread = Thread.current
-
-
1
autoload :CucumberTags, 'vcr/test_frameworks/cucumber'
-
1
autoload :InternetConnection, 'vcr/util/internet_connection'
-
-
1
module RSpec
-
1
autoload :Metadata, 'vcr/test_frameworks/rspec'
-
1
autoload :Macros, 'vcr/deprecations'
-
end
-
-
1
module Middleware
-
1
autoload :Faraday, 'vcr/middleware/faraday'
-
1
autoload :Rack, 'vcr/middleware/rack'
-
end
-
-
# The currently active cassette.
-
#
-
# @return [nil, VCR::Cassette] The current cassette or nil if there is
-
# no current cassette.
-
1
def current_cassette
-
20
cassettes.last
-
end
-
-
# Inserts the named cassette using the given cassette options.
-
# New HTTP interactions, if allowed by the cassette's `:record` option, will
-
# be recorded to the cassette. The cassette's existing HTTP interactions
-
# will be used to stub requests, unless prevented by the cassete's
-
# `:record` option.
-
#
-
# @example
-
# VCR.insert_cassette('twitter', :record => :new_episodes)
-
#
-
# # ...later, after making an HTTP request:
-
#
-
# VCR.eject_cassette
-
#
-
# @param name [#to_s] The name of the cassette. VCR will sanitize
-
# this to ensure it is a valid file name.
-
# @param options [Hash] The cassette options. The given options will
-
# be merged with the configured default_cassette_options.
-
# @option options :record [:all, :none, :new_episodes, :once] The record mode.
-
# @option options :erb [Boolean, Hash] Whether or not to evaluate the
-
# cassette as an ERB template. Defaults to false. A hash can be used
-
# to provide the ERB template with local variables.
-
# @option options :match_requests_on [Array<Symbol, #call>] List of request matchers
-
# to use to determine what recorded HTTP interaction to replay. Defaults to
-
# [:method, :uri]. The built-in matchers are :method, :uri, :host, :path, :headers
-
# and :body. You can also pass the name of a registered custom request matcher or
-
# any object that responds to #call.
-
# @option options :re_record_interval [Integer] When given, the
-
# cassette will be re-recorded at the given interval, in seconds.
-
# @option options :tag [Symbol] Used to apply tagged `before_record`
-
# and `before_playback` hooks to the cassette.
-
# @option options :tags [Array<Symbol>] Used to apply multiple tags to
-
# a cassette so that tagged `before_record` and `before_playback` hooks
-
# will apply to the cassette.
-
# @option options :update_content_length_header [Boolean] Whether or
-
# not to overwrite the Content-Length header of the responses to
-
# match the length of the response body. Defaults to false.
-
# @option options :decode_compressed_response [Boolean] Whether or
-
# not to decode compressed responses before recording the cassette.
-
# This makes the cassette more human readable. Defaults to false.
-
# @option options :allow_playback_repeats [Boolean] Whether or not to
-
# allow a single HTTP interaction to be played back multiple times.
-
# Defaults to false.
-
# @option options :allow_unused_http_interactions [Boolean] If set to
-
# false, an error will be raised if a cassette is ejected before all
-
# previously recorded HTTP interactions have been used.
-
# Defaults to true. Note that when an error has already occurred
-
# (as indicated by the `$!` variable) unused interactions will be
-
# allowed so that we don't silence the original error (which is almost
-
# certainly more interesting/important).
-
# @option options :exclusive [Boolean] Whether or not to use only this
-
# cassette and to completely ignore any cassettes in the cassettes stack.
-
# Defaults to false.
-
# @option options :serialize_with [Symbol] Which serializer to use.
-
# Valid values are :yaml, :syck, :psych, :json or any registered
-
# custom serializer. Defaults to :yaml.
-
# @option options :persist_with [Symbol] Which cassette persister to
-
# use. Defaults to :file_system. You can also register and use a
-
# custom persister.
-
# @option options :preserve_exact_body_bytes [Boolean] Whether or not
-
# to base64 encode the bytes of the requests and responses for this cassette
-
# when serializing it. See also `VCR::Configuration#preserve_exact_body_bytes`.
-
#
-
# @return [VCR::Cassette] the inserted cassette
-
#
-
# @raise [ArgumentError] when the given cassette is already being used.
-
# @raise [VCR::Errors::TurnedOffError] when VCR has been turned off
-
# without using the :ignore_cassettes option.
-
# @raise [VCR::Errors::MissingERBVariableError] when the `:erb` option
-
# is used and the ERB template requires variables that you did not provide.
-
#
-
# @note If you use this method you _must_ call `eject_cassette` when you
-
# are done. It is generally recommended that you use {#use_cassette}
-
# unless your code-under-test cannot be run as a block.
-
#
-
1
def insert_cassette(name, options = {})
-
4
if turned_on?
-
4
if cassettes.any? { |c| c.name == name }
-
raise ArgumentError.new("There is already a cassette with the same name (#{name}). You cannot nest multiple cassettes with the same name.")
-
end
-
-
4
cassette = Cassette.new(name, options)
-
4
context_cassettes.push(cassette)
-
4
cassette
-
elsif !ignore_cassettes?
-
message = "VCR is turned off. You must turn it on before you can insert a cassette. " +
-
"Or you can use the `:ignore_cassettes => true` option to completely ignore cassette insertions."
-
raise TurnedOffError.new(message)
-
end
-
end
-
-
# Ejects the current cassette. The cassette will no longer be used.
-
# In addition, any newly recorded HTTP interactions will be written to
-
# disk.
-
#
-
# @param options [Hash] Eject options.
-
# @option options :skip_no_unused_interactions_assertion [Boolean]
-
# If `true` is given, this will skip the "no unused HTTP interactions"
-
# assertion enabled by the `:allow_unused_http_interactions => false`
-
# cassette option. This is intended for use when your test has had
-
# an error, but your test framework has already handled it.
-
# @return [VCR::Cassette, nil] the ejected cassette if there was one
-
1
def eject_cassette(options = {})
-
4
cassette = cassettes.last
-
4
cassette.eject(options) if cassette
-
4
cassette
-
ensure
-
4
context_cassettes.delete(cassette)
-
end
-
-
# Inserts a cassette using the given name and options, runs the given
-
# block, and ejects the cassette.
-
#
-
# @example
-
# VCR.use_cassette('twitter', :record => :new_episodes) do
-
# # make an HTTP request
-
# end
-
#
-
# @param (see #insert_cassette)
-
# @option (see #insert_cassette)
-
# @yield Block to run while this cassette is in use.
-
# @yieldparam cassette [(optional) VCR::Cassette] the cassette that has
-
# been inserted.
-
# @raise (see #insert_cassette)
-
# @return [void]
-
# @see #insert_cassette
-
# @see #eject_cassette
-
1
def use_cassette(name, options = {}, &block)
-
4
unless block
-
raise ArgumentError, "`VCR.use_cassette` requires a block. " +
-
"If you cannot wrap your code in a block, use " +
-
"`VCR.insert_cassette` / `VCR.eject_cassette` instead."
-
end
-
-
4
cassette = insert_cassette(name, options)
-
-
4
begin
-
4
call_block(block, cassette)
-
ensure
-
4
eject_cassette
-
end
-
end
-
-
# Used to configure VCR.
-
#
-
# @example
-
# VCR.configure do |c|
-
# c.some_config_option = true
-
# end
-
#
-
# @yield the configuration block
-
# @yieldparam config [VCR::Configuration] the configuration object
-
# @return [void]
-
1
def configure
-
1
yield configuration
-
end
-
-
# @return [VCR::Configuration] the VCR configuration.
-
1
def configuration
-
117
@configuration
-
end
-
-
# Sets up `Before` and `After` cucumber hooks in order to
-
# use VCR with particular cucumber tags.
-
#
-
# @example
-
# VCR.cucumber_tags do |t|
-
# t.tags "tag1", "tag2"
-
# t.tag "@some_other_tag", :record => :new_episodes
-
# end
-
#
-
# @yield the cucumber tags configuration block
-
# @yieldparam t [VCR::CucumberTags] Cucumber tags config object
-
# @return [void]
-
# @see VCR::CucumberTags#tags
-
1
def cucumber_tags(&block)
-
main_object = eval('self', block.binding)
-
yield VCR::CucumberTags.new(main_object)
-
end
-
-
# Turns VCR off for the duration of a block.
-
#
-
# @param (see #turn_off!)
-
# @return [void]
-
# @raise (see #turn_off!)
-
# @see #turn_off!
-
# @see #turn_on!
-
# @see #turned_on?
-
1
def turned_off(options = {})
-
turn_off!(options)
-
-
begin
-
yield
-
ensure
-
turn_on!
-
end
-
end
-
-
# Turns VCR off, so that it no longer handles every HTTP request.
-
#
-
# @param options [Hash] hash of options
-
# @option options :ignore_cassettes [Boolean] controls what happens when a cassette is
-
# inserted while VCR is turned off. If `true` is passed, the cassette insertion
-
# will be ignored; otherwise a {VCR::Errors::TurnedOffError} will be raised.
-
#
-
# @return [void]
-
# @raise [VCR::Errors::CassetteInUseError] if there is currently a cassette in use
-
# @raise [ArgumentError] if you pass an invalid option
-
1
def turn_off!(options = {})
-
if VCR.current_cassette
-
raise CassetteInUseError, "A VCR cassette is currently in use (#{VCR.current_cassette.name}). " +
-
"You must eject it before you can turn VCR off."
-
end
-
-
set_context_value(:ignore_cassettes, options.fetch(:ignore_cassettes, false))
-
invalid_options = options.keys - [:ignore_cassettes]
-
if invalid_options.any?
-
raise ArgumentError.new("You passed some invalid options: #{invalid_options.inspect}")
-
end
-
-
set_context_value(:turned_off, true)
-
end
-
-
# Turns on VCR, if it has previously been turned off.
-
# @return [void]
-
# @see #turn_off!
-
# @see #turned_off
-
# @see #turned_on?
-
1
def turn_on!
-
set_context_value(:turned_off, false)
-
end
-
-
# @return whether or not VCR is turned on
-
# @note Normally VCR is _always_ turned on; it will only be off if you have
-
# explicitly turned it off.
-
# @see #turn_on!
-
# @see #turn_off!
-
# @see #turned_off
-
1
def turned_on?
-
4
!context_value(:turned_off)
-
end
-
-
# @private
-
1
def http_interactions
-
8
return current_cassette.http_interactions if current_cassette
-
4
VCR::Cassette::HTTPInteractionList::NullList
-
end
-
-
# @private
-
1
def real_http_connections_allowed?
-
return current_cassette.recording? if current_cassette
-
!!(configuration.allow_http_connections_when_no_cassette? || !turned_on?)
-
end
-
-
# @return [RequestMatcherRegistry] the request matcher registry
-
1
def request_matchers
-
9
@request_matchers
-
end
-
-
# @return [Enumerable] list of all cassettes currently being used
-
1
def cassettes(context = current_context)
-
28
linked_context = context[:linked_context]
-
28
linked_cassettes = cassettes(linked_context) if linked_context
-
-
28
LinkedCassette.list(context[:cassettes], Array(linked_cassettes))
-
end
-
-
# @private
-
1
def request_ignorer
-
8
@request_ignorer
-
end
-
-
# @private
-
1
def library_hooks
-
16
@library_hooks
-
end
-
-
# @private
-
1
def cassette_serializers
-
4
@cassette_serializers
-
end
-
-
# @private
-
1
def cassette_persisters
-
5
@cassette_persisters
-
end
-
-
# @private
-
1
def record_http_interaction(interaction)
-
return unless cassette = current_cassette
-
return if VCR.request_ignorer.ignore?(interaction.request)
-
-
cassette.record_http_interaction(interaction)
-
end
-
-
# @private
-
1
def link_context(from_thread, to_key)
-
@context[to_key] = get_context(from_thread)
-
end
-
-
# @private
-
1
def unlink_context(key)
-
@context.delete(key)
-
end
-
-
# @private
-
1
def fibers_available?
-
@fibers_available
-
end
-
-
1
private
-
1
def current_context
-
40
get_context(Thread.current, Fiber.current)
-
end
-
-
1
def get_context(thread_key, fiber_key = nil)
-
40
context = @context[fiber_key] if fiber_key
-
40
context ||= @context[thread_key]
-
40
if context
-
40
context
-
else
-
@context[thread_key] = dup_context(@context[MainThread])
-
end
-
end
-
-
1
def context_value(name)
-
12
current_context[name]
-
end
-
-
1
def set_context_value(name, value)
-
current_context[name] = value
-
end
-
-
1
def dup_context(context)
-
{
-
:turned_off => context[:turned_off],
-
:ignore_cassettes => context[:ignore_cassettes],
-
:cassettes => [],
-
:linked_context => context
-
}
-
end
-
-
1
def ignore_cassettes?
-
context_value(:ignore_cassettes)
-
end
-
-
1
def context_cassettes
-
8
context_value(:cassettes)
-
end
-
-
1
def initialize_fibers
-
1
begin
-
1
require 'fiber'
-
1
@fibers_available = true
-
rescue LoadError
-
@fibers_available = false
-
end
-
end
-
-
1
def initialize_ivars
-
1
initialize_fibers
-
1
@context = {
-
MainThread => {
-
:turned_off => false,
-
:ignore_cassettes => false,
-
:cassettes => [],
-
:linked_context => nil
-
}
-
}
-
1
@configuration = Configuration.new
-
1
@request_matchers = RequestMatcherRegistry.new
-
1
@request_ignorer = RequestIgnorer.new
-
1
@library_hooks = LibraryHooks.new
-
1
@cassette_serializers = Cassette::Serializers.new
-
1
@cassette_persisters = Cassette::Persisters.new
-
end
-
-
1
initialize_ivars # to avoid warnings
-
end
-
1
require 'vcr/cassette/http_interaction_list'
-
1
require 'vcr/cassette/erb_renderer'
-
1
require 'vcr/cassette/serializers'
-
-
1
module VCR
-
# The media VCR uses to store HTTP interactions for later re-use.
-
1
class Cassette
-
1
include Logger::Mixin
-
-
# The supported record modes.
-
#
-
# * :all -- Record every HTTP interactions; do not play any back.
-
# * :none -- Do not record any HTTP interactions; play them back.
-
# * :new_episodes -- Playback previously recorded HTTP interactions and record new ones.
-
# * :once -- Record the HTTP interactions if the cassette has not already been recorded;
-
# otherwise, playback the HTTP interactions.
-
1
VALID_RECORD_MODES = [:all, :none, :new_episodes, :once]
-
-
# @return [#to_s] The name of the cassette. Used to determine the cassette's file name.
-
# @see #file
-
1
attr_reader :name
-
-
# @return [Symbol] The record mode. Determines whether the cassette records HTTP interactions,
-
# plays them back, or does both.
-
1
attr_reader :record_mode
-
-
# @return [Array<Symbol, #call>] List of request matchers. Used to find a response from an
-
# existing HTTP interaction to play back.
-
1
attr_reader :match_requests_on
-
-
# @return [Boolean, Hash] The cassette's ERB option. The file will be treated as an
-
# ERB template if this has a truthy value. A hash, if provided, will be used as local
-
# variables for the ERB template.
-
1
attr_reader :erb
-
-
# @return [Integer, nil] How frequently (in seconds) the cassette should be re-recorded.
-
1
attr_reader :re_record_interval
-
-
# @return [Array<Symbol>] If set, {VCR::Configuration#before_record} and
-
# {VCR::Configuration#before_playback} hooks with a corresponding tag will apply.
-
1
attr_reader :tags
-
-
# @param (see VCR#insert_cassette)
-
# @see VCR#insert_cassette
-
1
def initialize(name, options = {})
-
4
@name = name
-
4
@options = VCR.configuration.default_cassette_options.merge(options)
-
-
4
assert_valid_options!
-
4
extract_options
-
4
raise_error_unless_valid_record_mode
-
-
4
log "Initialized with options: #{@options.inspect}"
-
end
-
-
# Ejects the current cassette. The cassette will no longer be used.
-
# In addition, any newly recorded HTTP interactions will be written to
-
# disk.
-
#
-
# @note This is not intended to be called directly. Use `VCR.eject_cassette` instead.
-
#
-
# @param (see VCR#eject_casssette)
-
# @see VCR#eject_cassette
-
1
def eject(options = {})
-
4
write_recorded_interactions_to_disk
-
-
4
if should_assert_no_unused_interactions? && !options[:skip_no_unused_interactions_assertion]
-
http_interactions.assert_no_unused_interactions!
-
end
-
end
-
-
# @private
-
1
def http_interactions
-
@http_interactions ||= HTTPInteractionList.new \
-
should_stub_requests? ? previously_recorded_interactions : [],
-
match_requests_on,
-
@allow_playback_repeats,
-
@parent_list,
-
4
log_prefix
-
end
-
-
# @private
-
1
def record_http_interaction(interaction)
-
VCR::CassetteMutex.synchronize do
-
log "Recorded HTTP interaction #{request_summary(interaction.request)} => #{response_summary(interaction.response)}"
-
new_recorded_interactions << interaction
-
end
-
end
-
-
# @private
-
1
def new_recorded_interactions
-
4
@new_recorded_interactions ||= []
-
end
-
-
# @return [String] The file for this cassette.
-
# @raise [NotImplementedError] if the configured cassette persister
-
# does not support resolving file paths.
-
# @note VCR will take care of sanitizing the cassette name to make it a valid file name.
-
1
def file
-
unless @persister.respond_to?(:absolute_path_to_file)
-
raise NotImplementedError, "The configured cassette persister does not support resolving file paths"
-
end
-
@persister.absolute_path_to_file(storage_key)
-
end
-
-
# @return [Boolean] Whether or not the cassette is recording.
-
1
def recording?
-
case record_mode
-
when :none; false
-
when :once; raw_cassette_bytes.to_s.empty?
-
else true
-
end
-
end
-
-
# @return [Hash] The hash that will be serialized when the cassette is written to disk.
-
1
def serializable_hash
-
{
-
"http_interactions" => interactions_to_record.map(&:to_hash),
-
"recorded_with" => "VCR #{VCR.version}"
-
}
-
end
-
-
# @return [Time, nil] The `recorded_at` time of the first HTTP interaction
-
# or nil if the cassette has no prior HTTP interactions.
-
#
-
# @example
-
#
-
# VCR.use_cassette("some cassette") do |cassette|
-
# Timecop.freeze(cassette.originally_recorded_at || Time.now) do
-
# # ...
-
# end
-
# end
-
1
def originally_recorded_at
-
@originally_recorded_at ||= previously_recorded_interactions.map(&:recorded_at).min
-
end
-
-
# @return [Boolean] false unless wrapped with LinkedCassette
-
1
def linked?
-
false
-
end
-
-
1
private
-
-
1
def assert_valid_options!
-
4
invalid_options = @options.keys - [
-
:record, :erb, :match_requests_on, :re_record_interval, :tag, :tags,
-
:update_content_length_header, :allow_playback_repeats, :allow_unused_http_interactions,
-
:exclusive, :serialize_with, :preserve_exact_body_bytes, :decode_compressed_response,
-
:persist_with
-
]
-
-
4
if invalid_options.size > 0
-
raise ArgumentError.new("You passed the following invalid options to VCR::Cassette.new: #{invalid_options.inspect}.")
-
end
-
end
-
-
1
def extract_options
-
[:erb, :match_requests_on, :re_record_interval,
-
4
:allow_playback_repeats, :allow_unused_http_interactions, :exclusive].each do |name|
-
24
instance_variable_set("@#{name}", @options[name])
-
end
-
-
4
assign_tags
-
-
4
@record_mode = @options[:record]
-
4
@serializer = VCR.cassette_serializers[@options[:serialize_with]]
-
4
@persister = VCR.cassette_persisters[@options[:persist_with]]
-
4
@record_mode = :all if should_re_record?
-
4
@parent_list = @exclusive ? HTTPInteractionList::NullList : VCR.http_interactions
-
end
-
-
1
def assign_tags
-
8
@tags = Array(@options.fetch(:tags) { @options[:tag] })
-
-
4
[:update_content_length_header, :preserve_exact_body_bytes, :decode_compressed_response].each do |tag|
-
12
@tags << tag if @options[tag]
-
end
-
end
-
-
1
def previously_recorded_interactions
-
@previously_recorded_interactions ||= if !raw_cassette_bytes.to_s.empty?
-
8
deserialized_hash['http_interactions'].map { |h| HTTPInteraction.from_hash(h) }.tap do |interactions|
-
4
invoke_hook(:before_playback, interactions)
-
-
4
interactions.reject! do |i|
-
4
i.request.uri.is_a?(String) && VCR.request_ignorer.ignore?(i.request)
-
end
-
end
-
else
-
[]
-
4
end
-
end
-
-
1
def storage_key
-
4
@storage_key ||= [name, @serializer.file_extension].join('.')
-
end
-
-
1
def raise_error_unless_valid_record_mode
-
4
unless VALID_RECORD_MODES.include?(record_mode)
-
raise ArgumentError.new("#{record_mode} is not a valid cassette record mode. Valid modes are: #{VALID_RECORD_MODES.inspect}")
-
end
-
end
-
-
1
def should_re_record?
-
4
return false unless @re_record_interval
-
return false unless originally_recorded_at
-
-
now = Time.now
-
-
(originally_recorded_at + @re_record_interval < now).tap do |value|
-
info = "previously recorded at: '#{originally_recorded_at}'; now: '#{now}'; interval: #{@re_record_interval} seconds"
-
-
if !value
-
log "Not re-recording since the interval has not elapsed (#{info})."
-
elsif InternetConnection.available?
-
log "re-recording (#{info})."
-
else
-
log "Not re-recording because no internet connection is available (#{info})."
-
return false
-
end
-
end
-
end
-
-
1
def should_stub_requests?
-
4
record_mode != :all
-
end
-
-
1
def should_remove_matching_existing_interactions?
-
record_mode == :all
-
end
-
-
1
def should_assert_no_unused_interactions?
-
4
!(@allow_unused_http_interactions || $!)
-
end
-
-
1
def raw_cassette_bytes
-
8
@raw_cassette_bytes ||= VCR::Cassette::ERBRenderer.new(@persister[storage_key], erb, name).render
-
end
-
-
1
def merged_interactions
-
old_interactions = previously_recorded_interactions
-
-
if should_remove_matching_existing_interactions?
-
new_interaction_list = HTTPInteractionList.new(new_recorded_interactions, match_requests_on)
-
old_interactions = old_interactions.reject do |i|
-
new_interaction_list.response_for(i.request)
-
end
-
end
-
-
old_interactions + new_recorded_interactions
-
end
-
-
1
def interactions_to_record
-
# We deep-dup the interactions by roundtripping them to/from a hash.
-
# This is necessary because `before_record` can mutate the interactions.
-
merged_interactions.map { |i| HTTPInteraction.from_hash(i.to_hash) }.tap do |interactions|
-
invoke_hook(:before_record, interactions)
-
end
-
end
-
-
1
def write_recorded_interactions_to_disk
-
4
return if new_recorded_interactions.none?
-
hash = serializable_hash
-
return if hash["http_interactions"].none?
-
-
@persister[storage_key] = @serializer.serialize(hash)
-
end
-
-
1
def invoke_hook(type, interactions)
-
4
interactions.delete_if do |i|
-
i.hook_aware.tap do |hw|
-
4
VCR.configuration.invoke_hook(type, hw, self)
-
4
end.ignored?
-
end
-
end
-
-
1
def deserialized_hash
-
@deserialized_hash ||= @serializer.deserialize(raw_cassette_bytes).tap do |hash|
-
4
unless hash.is_a?(Hash) && hash['http_interactions'].is_a?(Array)
-
raise Errors::InvalidCassetteFormatError.new \
-
"#{file} does not appear to be a valid VCR 2.0 cassette. " +
-
"VCR 1.x cassettes are not valid with VCR 2.0. When upgrading from " +
-
"VCR 1.x, it is recommended that you delete all your existing cassettes and " +
-
"re-record them, or use the provided vcr:migrate_cassettes rake task to migrate " +
-
"them. For more info, see the VCR upgrade guide."
-
end
-
4
end
-
end
-
-
1
def log_prefix
-
8
@log_prefix ||= "[Cassette: '#{name}'] "
-
end
-
-
1
def request_summary(request)
-
super(request, match_requests_on)
-
end
-
end
-
end
-
1
require 'erb'
-
-
1
module VCR
-
1
class Cassette
-
# @private
-
1
class ERBRenderer
-
1
def initialize(raw_template, erb, cassette_name=nil)
-
4
@raw_template, @erb, @cassette_name = raw_template, erb, cassette_name
-
end
-
-
1
def render
-
4
return @raw_template if @raw_template.nil? || !use_erb?
-
binding = binding_for_variables if erb_variables
-
template.result(binding)
-
rescue NameError => e
-
handle_name_error(e)
-
end
-
-
1
private
-
-
1
def handle_name_error(e)
-
example_hash = (erb_variables || {}).merge(e.name => 'some value')
-
-
raise Errors::MissingERBVariableError.new(
-
"The ERB in the #{@cassette_name} cassette file references undefined variable #{e.name}. " +
-
"Pass it to the cassette using :erb => #{ example_hash.inspect }."
-
)
-
end
-
-
1
def use_erb?
-
4
!!@erb
-
end
-
-
1
def erb_variables
-
@erb if @erb.is_a?(Hash)
-
end
-
-
1
def template
-
@template ||= ERB.new(@raw_template)
-
end
-
-
1
@@struct_cache = Hash.new do |hash, attributes|
-
hash[attributes] = Struct.new(*attributes)
-
end
-
-
1
def variables_object
-
@variables_object ||= @@struct_cache[erb_variables.keys].new(*erb_variables.values)
-
end
-
-
1
def binding_for_variables
-
@binding_for_variables ||= variables_object.instance_eval { binding }
-
end
-
end
-
end
-
end
-
1
module VCR
-
1
class Cassette
-
# @private
-
1
class HTTPInteractionList
-
1
include Logger::Mixin
-
-
# @private
-
1
module NullList
-
1
extend self
-
1
def response_for(*a); nil; end
-
1
def has_interaction_matching?(*a); false; end
-
1
def has_used_interaction_matching?(*a); false; end
-
1
def remaining_unused_interaction_count(*a); 0; end
-
end
-
-
1
attr_reader :interactions, :request_matchers, :allow_playback_repeats, :parent_list
-
-
1
def initialize(interactions, request_matchers, allow_playback_repeats = false, parent_list = NullList, log_prefix = '')
-
4
@interactions = interactions.dup
-
4
@request_matchers = request_matchers
-
4
@allow_playback_repeats = allow_playback_repeats
-
4
@parent_list = parent_list
-
4
@used_interactions = []
-
4
@log_prefix = log_prefix
-
-
8
interaction_summaries = interactions.map { |i| "#{request_summary(i.request)} => #{response_summary(i.response)}" }
-
4
log "Initialized HTTPInteractionList with request matchers #{request_matchers.inspect} and #{interactions.size} interaction(s): { #{interaction_summaries.join(', ')} }", 1
-
end
-
-
1
def response_for(request)
-
4
if index = matching_interaction_index_for(request)
-
4
interaction = @interactions.delete_at(index)
-
4
@used_interactions.unshift interaction
-
4
log "Found matching interaction for #{request_summary(request)} at index #{index}: #{response_summary(interaction.response)}", 1
-
4
interaction.response
-
elsif interaction = matching_used_interaction_for(request)
-
interaction.response
-
else
-
@parent_list.response_for(request)
-
end
-
end
-
-
1
def has_interaction_matching?(request)
-
!!matching_interaction_index_for(request) ||
-
!!matching_used_interaction_for(request) ||
-
@parent_list.has_interaction_matching?(request)
-
end
-
-
1
def has_used_interaction_matching?(request)
-
@used_interactions.any? { |i| interaction_matches_request?(request, i) }
-
end
-
-
1
def remaining_unused_interaction_count
-
@interactions.size
-
end
-
-
# Checks if there are no unused interactions left.
-
#
-
# @raise [VCR::Errors::UnusedHTTPInteractionError] if not all interactions were played back.
-
1
def assert_no_unused_interactions!
-
return unless has_unused_interactions?
-
logger = Logger.new(nil)
-
-
descriptions = @interactions.map do |i|
-
" - #{logger.request_summary(i.request, @request_matchers)} => #{logger.response_summary(i.response)}"
-
end.join("\n")
-
-
raise Errors::UnusedHTTPInteractionError, "There are unused HTTP interactions left in the cassette:\n#{descriptions}"
-
end
-
-
1
private
-
-
# @return [Boolean] Whether or not there are unused interactions left in the list.
-
1
def has_unused_interactions?
-
@interactions.size > 0
-
end
-
-
1
def request_summary(request)
-
32
super(request, @request_matchers)
-
end
-
-
1
def matching_interaction_index_for(request)
-
8
@interactions.index { |i| interaction_matches_request?(request, i) }
-
end
-
-
1
def matching_used_interaction_for(request)
-
return nil unless @allow_playback_repeats
-
@used_interactions.find { |i| interaction_matches_request?(request, i) }
-
end
-
-
1
def interaction_matches_request?(request, interaction)
-
4
log "Checking if #{request_summary(request)} matches #{request_summary(interaction.request)} using #{@request_matchers.inspect}", 1
-
4
@request_matchers.all? do |matcher_name|
-
8
matcher = VCR.request_matchers[matcher_name]
-
8
matcher.matches?(request, interaction.request).tap do |matched|
-
8
matched = matched ? 'matched' : 'did not match'
-
8
log "#{matcher_name} (#{matched}): current request #{request_summary(request)} vs #{request_summary(interaction.request)}", 2
-
end
-
end
-
end
-
-
1
def log_prefix
-
20
@log_prefix
-
end
-
end
-
end
-
end
-
-
1
module VCR
-
1
class Cassette
-
# Keeps track of the cassette persisters in a hash-like object.
-
1
class Persisters
-
1
autoload :FileSystem, 'vcr/cassette/persisters/file_system'
-
-
# @private
-
1
def initialize
-
1
@persisters = {}
-
end
-
-
# Gets the named persister.
-
#
-
# @param name [Symbol] the name of the persister
-
# @return the named persister
-
# @raise [ArgumentError] if there is not a persister for the given name
-
1
def [](name)
-
5
@persisters.fetch(name) do |_|
-
1
@persisters[name] = case name
-
1
when :file_system then FileSystem
-
else raise ArgumentError, "The requested VCR cassette persister " +
-
"(#{name.inspect}) is not registered."
-
end
-
end
-
end
-
-
# Registers a persister.
-
#
-
# @param name [Symbol] the name of the persister
-
# @param value [#[], #[]=] the persister object. It must implement `[]` and `[]=`.
-
1
def []=(name, value)
-
if @persisters.has_key?(name)
-
warn "WARNING: There is already a VCR cassette persister " +
-
"registered for #{name.inspect}. Overriding it."
-
end
-
-
@persisters[name] = value
-
end
-
-
end
-
end
-
end
-
1
require 'fileutils'
-
-
1
module VCR
-
1
class Cassette
-
1
class Persisters
-
# The only built-in cassette persister. Persists cassettes to the file system.
-
1
module FileSystem
-
1
extend self
-
-
# @private
-
1
attr_reader :storage_location
-
-
# @private
-
1
def storage_location=(dir)
-
1
FileUtils.mkdir_p(dir) if dir
-
1
@storage_location = dir ? absolute_path_for(dir) : nil
-
end
-
-
# Gets the cassette for the given storage key (file name).
-
#
-
# @param [String] file_name the file name
-
# @return [String] the cassette content
-
1
def [](file_name)
-
4
path = absolute_path_to_file(file_name)
-
4
return nil unless File.exist?(path)
-
4
File.binread(path)
-
end
-
-
# Sets the cassette for the given storage key (file name).
-
#
-
# @param [String] file_name the file name
-
# @param [String] content the content to store
-
1
def []=(file_name, content)
-
path = absolute_path_to_file(file_name)
-
directory = File.dirname(path)
-
FileUtils.mkdir_p(directory) unless File.exist?(directory)
-
File.binwrite(path, content)
-
end
-
-
# @private
-
1
def absolute_path_to_file(file_name)
-
4
return nil unless storage_location
-
4
File.join(storage_location, sanitized_file_name_from(file_name))
-
end
-
-
1
private
-
1
def absolute_path_for(path)
-
2
Dir.chdir(path) { Dir.pwd }
-
end
-
-
1
def sanitized_file_name_from(file_name)
-
4
parts = file_name.to_s.split('.')
-
-
4
if parts.size > 1 && !parts.last.include?(File::SEPARATOR)
-
4
file_extension = '.' + parts.pop
-
end
-
-
4
parts.join('.').gsub(/[^\w\-\/]+/, '_') + file_extension.to_s
-
end
-
end
-
end
-
end
-
end
-
1
module VCR
-
1
class Cassette
-
# Keeps track of the cassette serializers in a hash-like object.
-
1
class Serializers
-
1
autoload :YAML, 'vcr/cassette/serializers/yaml'
-
1
autoload :Syck, 'vcr/cassette/serializers/syck'
-
1
autoload :Psych, 'vcr/cassette/serializers/psych'
-
1
autoload :JSON, 'vcr/cassette/serializers/json'
-
1
autoload :Compressed, 'vcr/cassette/serializers/compressed'
-
-
# @private
-
1
def initialize
-
1
@serializers = {}
-
end
-
-
# Gets the named serializer.
-
#
-
# @param name [Symbol] the name of the serializer
-
# @return the named serializer
-
# @raise [ArgumentError] if there is not a serializer for the given name
-
1
def [](name)
-
4
@serializers.fetch(name) do |_|
-
1
@serializers[name] = case name
-
1
when :yaml then YAML
-
when :syck then Syck
-
when :psych then Psych
-
when :json then JSON
-
when :compressed then Compressed
-
else raise ArgumentError.new("The requested VCR cassette serializer (#{name.inspect}) is not registered.")
-
end
-
end
-
end
-
-
# Registers a serializer.
-
#
-
# @param name [Symbol] the name of the serializer
-
# @param value [#file_extension, #serialize, #deserialize] the serializer object. It must implement
-
# `file_extension()`, `serialize(Hash)` and `deserialize(String)`.
-
1
def []=(name, value)
-
if @serializers.has_key?(name)
-
warn "WARNING: There is already a VCR cassette serializer registered for #{name.inspect}. Overriding it."
-
end
-
-
@serializers[name] = value
-
end
-
end
-
-
# @private
-
1
module EncodingErrorHandling
-
1
def handle_encoding_errors
-
4
yield
-
rescue *self::ENCODING_ERRORS => e
-
e.message << "\nNote: Using VCR's `:preserve_exact_body_bytes` option may help prevent this error in the future."
-
raise
-
end
-
end
-
end
-
end
-
-
1
require 'yaml'
-
-
1
module VCR
-
1
class Cassette
-
1
class Serializers
-
# The YAML serializer. This will use either Psych or Syck, which ever your
-
# ruby interpreter defaults to. You can also force VCR to use Psych or Syck by
-
# using one of those serializers.
-
#
-
# @see JSON
-
# @see Psych
-
# @see Syck
-
1
module YAML
-
1
extend self
-
1
extend EncodingErrorHandling
-
-
# @private
-
1
ENCODING_ERRORS = [ArgumentError]
-
-
# The file extension to use for this serializer.
-
#
-
# @return [String] "yml"
-
1
def file_extension
-
4
"yml"
-
end
-
-
# Serializes the given hash using YAML.
-
#
-
# @param [Hash] hash the object to serialize
-
# @return [String] the YAML string
-
1
def serialize(hash)
-
handle_encoding_errors do
-
::YAML.dump(hash)
-
end
-
end
-
-
# Deserializes the given string using YAML.
-
#
-
# @param [String] string the YAML string
-
# @return [Hash] the deserialized object
-
1
def deserialize(string)
-
4
handle_encoding_errors do
-
4
::YAML.load(string)
-
end
-
end
-
end
-
end
-
end
-
end
-
-
1
require 'vcr/util/hooks'
-
1
require 'uri'
-
1
require 'cgi'
-
-
1
module VCR
-
# Stores the VCR configuration.
-
1
class Configuration
-
1
include Hooks
-
1
include VariableArgsBlockCaller
-
1
include Logger::Mixin
-
-
# Gets the directory to read cassettes from and write cassettes to.
-
#
-
# @return [String] the directory to read cassettes from and write cassettes to
-
1
def cassette_library_dir
-
VCR.cassette_persisters[:file_system].storage_location
-
end
-
-
# Sets the directory to read cassettes from and writes cassettes to.
-
#
-
# @example
-
# VCR.configure do |c|
-
# c.cassette_library_dir = 'spec/cassettes'
-
# end
-
#
-
# @param dir [String] the directory to read cassettes from and write cassettes to
-
# @return [void]
-
# @note This is only necessary if you use the `:file_system`
-
# cassette persister (the default).
-
1
def cassette_library_dir=(dir)
-
1
VCR.cassette_persisters[:file_system].storage_location = dir
-
end
-
-
# Default options to apply to every cassette.
-
#
-
# @overload default_cassette_options
-
# @return [Hash] default options to apply to every cassette
-
# @overload default_cassette_options=(options)
-
# @param options [Hash] default options to apply to every cassette
-
# @return [void]
-
# @example
-
# VCR.configure do |c|
-
# c.default_cassette_options = { :record => :new_episodes }
-
# end
-
# @note {VCR#insert_cassette} for the list of valid options.
-
1
attr_reader :default_cassette_options
-
-
# Sets the default options that apply to every cassette.
-
1
def default_cassette_options=(overrides)
-
1
@default_cassette_options.merge!(overrides)
-
end
-
-
# Configures which libraries VCR will hook into to intercept HTTP requests.
-
#
-
# @example
-
# VCR.configure do |c|
-
# c.hook_into :fakeweb, :typhoeus
-
# end
-
#
-
# @param hooks [Array<Symbol>] List of libraries. Valid values are
-
# `:fakeweb`, `:webmock`, `:typhoeus`, `:excon` and `:faraday`.
-
# @raise [ArgumentError] when given an unsupported library name.
-
# @raise [VCR::Errors::LibraryVersionTooLowError] when the version
-
# of a library you are using is too low for VCR to support.
-
# @note `:fakeweb` and `:webmock` cannot both be used since they both monkey patch
-
# `Net::HTTP`. Otherwise, you can use any combination of these.
-
1
def hook_into(*hooks)
-
2
hooks.each { |a| load_library_hook(a) }
-
1
invoke_hook(:after_library_hooks_loaded)
-
end
-
-
# Specifies host(s) that VCR should ignore.
-
#
-
# @param hosts [Array<String>] List of hosts to ignore
-
# @see #ignore_localhost=
-
# @see #ignore_request
-
1
def ignore_hosts(*hosts)
-
VCR.request_ignorer.ignore_hosts(*hosts)
-
end
-
1
alias ignore_host ignore_hosts
-
-
# Sets whether or not VCR should ignore localhost requests.
-
#
-
# @param value [Boolean] the value to set
-
# @see #ignore_hosts
-
# @see #ignore_request
-
1
def ignore_localhost=(value)
-
VCR.request_ignorer.ignore_localhost = value
-
end
-
-
# Defines what requests to ignore using a block.
-
#
-
# @example
-
# VCR.configure do |c|
-
# c.ignore_request do |request|
-
# uri = URI(request.uri)
-
# # ignore only localhost requests to port 7500
-
# uri.host == 'localhost' && uri.port == 7500
-
# end
-
# end
-
#
-
# @yield the callback
-
# @yieldparam request [VCR::Request] the HTTP request
-
# @yieldreturn [Boolean] whether or not to ignore the request
-
1
def ignore_request(&block)
-
VCR.request_ignorer.ignore_request(&block)
-
end
-
-
# Determines how VCR treats HTTP requests that are made when
-
# no VCR cassette is in use. When set to `true`, requests made
-
# when there is no VCR cassette in use will be allowed. When set
-
# to `false` (the default), an {VCR::Errors::UnhandledHTTPRequestError}
-
# will be raised for any HTTP request made when there is no
-
# cassette in use.
-
#
-
# @overload allow_http_connections_when_no_cassette?
-
# @return [Boolean] whether or not HTTP connections are allowed
-
# when there is no cassette.
-
# @overload allow_http_connections_when_no_cassette=
-
# @param value [Boolean] sets whether or not to allow HTTP
-
# connections when there is no cassette.
-
1
attr_writer :allow_http_connections_when_no_cassette
-
# @private (documented above)
-
1
def allow_http_connections_when_no_cassette?
-
!!@allow_http_connections_when_no_cassette
-
end
-
-
# Sets a parser for VCR to use when parsing query strings for request
-
# comparisons. The new parser must implement a method `call` that returns
-
# an object which is both equalivant and consistent when given an HTTP
-
# query string of possibly differing value ordering.
-
#
-
# * `#== # => Boolean`
-
#
-
# The `#==` method must return true if both objects represent the
-
# same query string.
-
#
-
# This defaults to `CGI.parse` from the ruby standard library.
-
#
-
# @overload query_parser
-
# @return [#call] the current query string parser object
-
# @overload query_parser=
-
# @param value [#call] sets the query_parser
-
1
attr_accessor :query_parser
-
-
# Sets a parser for VCR to use when parsing URIs. The new parser
-
# must implement a method `parse` that returns an instance of the
-
# URI object. This URI object must implement the following
-
# interface:
-
#
-
# * `scheme # => String`
-
# * `host # => String`
-
# * `port # => Fixnum`
-
# * `path # => String`
-
# * `query # => String`
-
# * `#port=`
-
# * `#query=`
-
# * `#to_s # => String`
-
# * `#== # => Boolean`
-
#
-
# The `#==` method must return true if both URI objects represent the
-
# same URI.
-
#
-
# This defaults to `URI` from the ruby standard library.
-
#
-
# @overload uri_parser
-
# @return [#parse] the current URI parser object
-
# @overload uri_parser=
-
# @param value [#parse] sets the uri_parser
-
1
attr_accessor :uri_parser
-
-
# Registers a request matcher for later use.
-
#
-
# @example
-
# VCR.configure do |c|
-
# c.register_request_matcher :port do |request_1, request_2|
-
# URI(request_1.uri).port == URI(request_2.uri).port
-
# end
-
# end
-
#
-
# VCR.use_cassette("my_cassette", :match_requests_on => [:method, :host, :port]) do
-
# # ...
-
# end
-
#
-
# @param name [Symbol] the name of the request matcher
-
# @yield the request matcher
-
# @yieldparam request_1 [VCR::Request] One request
-
# @yieldparam request_2 [VCR::Request] The other request
-
# @yieldreturn [Boolean] whether or not these two requests should be considered
-
# equivalent
-
1
def register_request_matcher(name, &block)
-
VCR.request_matchers.register(name, &block)
-
end
-
-
# Sets up a {#before_record} and a {#before_playback} hook that will
-
# insert a placeholder string in the cassette in place of another string.
-
# You can use this as a generic way to interpolate a variable into the
-
# cassette for a unique string. It's particularly useful for unique
-
# sensitive strings like API keys and passwords.
-
#
-
# @example
-
# VCR.configure do |c|
-
# # Put "<GITHUB_API_KEY>" in place of the actual API key in
-
# # our cassettes so we don't have to commit to source control.
-
# c.filter_sensitive_data('<GITHUB_API_KEY>') { GithubClient.api_key }
-
#
-
# # Put a "<USER_ID>" placeholder variable in our cassettes tagged with
-
# # :user_cassette since it can be different for different test runs.
-
# c.define_cassette_placeholder('<USER_ID>', :user_cassette) { User.last.id }
-
# end
-
#
-
# @param placeholder [String] The placeholder string.
-
# @param tag [Symbol] Set this to apply this only to cassettes
-
# with a matching tag; otherwise it will apply to every cassette.
-
# @yield block that determines what string to replace
-
# @yieldparam interaction [(optional) VCR::HTTPInteraction::HookAware] the HTTP interaction
-
# @yieldreturn the string to replace
-
1
def define_cassette_placeholder(placeholder, tag = nil, &block)
-
before_record(tag) do |interaction|
-
orig_text = call_block(block, interaction)
-
log "before_record: replacing #{orig_text.inspect} with #{placeholder.inspect}"
-
interaction.filter!(orig_text, placeholder)
-
end
-
-
before_playback(tag) do |interaction|
-
orig_text = call_block(block, interaction)
-
log "before_playback: replacing #{placeholder.inspect} with #{orig_text.inspect}"
-
interaction.filter!(placeholder, orig_text)
-
end
-
end
-
1
alias filter_sensitive_data define_cassette_placeholder
-
-
# Gets the registry of cassette serializers. Use it to register a custom serializer.
-
#
-
# @example
-
# VCR.configure do |c|
-
# c.cassette_serializers[:my_custom_serializer] = my_custom_serializer
-
# end
-
#
-
# @return [VCR::Cassette::Serializers] the cassette serializer registry object.
-
# @note Custom serializers must implement the following interface:
-
#
-
# * `file_extension # => String`
-
# * `serialize(Hash) # => String`
-
# * `deserialize(String) # => Hash`
-
1
def cassette_serializers
-
VCR.cassette_serializers
-
end
-
-
# Gets the registry of cassette persisters. Use it to register a custom persister.
-
#
-
# @example
-
# VCR.configure do |c|
-
# c.cassette_persisters[:my_custom_persister] = my_custom_persister
-
# end
-
#
-
# @return [VCR::Cassette::Persisters] the cassette persister registry object.
-
# @note Custom persisters must implement the following interface:
-
#
-
# * `persister[storage_key]` # returns previously persisted content
-
# * `persister[storage_key] = content` # persists given content
-
1
def cassette_persisters
-
VCR.cassette_persisters
-
end
-
-
1
define_hook :before_record
-
# Adds a callback that will be called before the recorded HTTP interactions
-
# are serialized and written to disk.
-
#
-
# @example
-
# VCR.configure do |c|
-
# # Don't record transient 5xx errors
-
# c.before_record do |interaction|
-
# interaction.ignore! if interaction.response.status.code >= 500
-
# end
-
#
-
# # Modify the response body for cassettes tagged with :twilio
-
# c.before_record(:twilio) do |interaction|
-
# interaction.response.body.downcase!
-
# end
-
# end
-
#
-
# @param tag [(optional) Symbol] Used to apply this hook to only cassettes that match
-
# the given tag.
-
# @yield the callback
-
# @yieldparam interaction [VCR::HTTPInteraction::HookAware] The interaction that will be
-
# serialized and written to disk.
-
# @yieldparam cassette [(optional) VCR::Cassette] The current cassette.
-
# @see #before_playback
-
1
def before_record(tag = nil, &block)
-
2
super(tag_filter_from(tag), &block)
-
end
-
-
1
define_hook :before_playback
-
# Adds a callback that will be called before a previously recorded
-
# HTTP interaction is loaded for playback.
-
#
-
# @example
-
# VCR.configure do |c|
-
# # Don't playback transient 5xx errors
-
# c.before_playback do |interaction|
-
# interaction.ignore! if interaction.response.status.code >= 500
-
# end
-
#
-
# # Change a response header for playback
-
# c.before_playback(:twilio) do |interaction|
-
# interaction.response.headers['X-Foo-Bar'] = 'Bazz'
-
# end
-
# end
-
#
-
# @param tag [(optional) Symbol] Used to apply this hook to only cassettes that match
-
# the given tag.
-
# @yield the callback
-
# @yieldparam interaction [VCR::HTTPInteraction::HookAware] The interaction that is being
-
# loaded.
-
# @yieldparam cassette [(optional) VCR::Cassette] The current cassette.
-
# @see #before_record
-
1
def before_playback(tag = nil, &block)
-
1
super(tag_filter_from(tag), &block)
-
end
-
-
# Adds a callback that will be called with each HTTP request before it is made.
-
#
-
# @example
-
# VCR.configure do |c|
-
# c.before_http_request(:real?) do |request|
-
# puts "Request: #{request.method} #{request.uri}"
-
# end
-
# end
-
#
-
# @param filters [optional splat of #to_proc] one or more filters to apply.
-
# The objects provided will be converted to procs using `#to_proc`. If provided,
-
# the callback will only be invoked if these procs all return `true`.
-
# @yield the callback
-
# @yieldparam request [VCR::Request::Typed] the request that is being made
-
# @see #after_http_request
-
# @see #around_http_request
-
1
define_hook :before_http_request
-
-
1
define_hook :after_http_request, :prepend
-
# Adds a callback that will be called with each HTTP request after it is complete.
-
#
-
# @example
-
# VCR.configure do |c|
-
# c.after_http_request(:ignored?) do |request, response|
-
# puts "Request: #{request.method} #{request.uri}"
-
# puts "Response: #{response.status.code}"
-
# end
-
# end
-
#
-
# @param filters [optional splat of #to_proc] one or more filters to apply.
-
# The objects provided will be converted to procs using `#to_proc`. If provided,
-
# the callback will only be invoked if these procs all return `true`.
-
# @yield the callback
-
# @yieldparam request [VCR::Request::Typed] the request that is being made
-
# @yieldparam response [VCR::Response] the response from the request
-
# @see #before_http_request
-
# @see #around_http_request
-
1
def after_http_request(*filters)
-
super(*filters.map { |f| request_filter_from(f) })
-
end
-
-
# Adds a callback that will be executed around each HTTP request.
-
#
-
# @example
-
# VCR.configure do |c|
-
# c.around_http_request(lambda {|r| r.uri =~ /api.geocoder.com/}) do |request|
-
# # extract an address like "1700 E Pine St, Seattle, WA"
-
# # from a query like "address=1700+E+Pine+St%2C+Seattle%2C+WA"
-
# address = CGI.unescape(URI(request.uri).query.split('=').last)
-
# VCR.use_cassette("geocoding/#{address}", &request)
-
# end
-
# end
-
#
-
# @yield the callback
-
# @yieldparam request [VCR::Request::FiberAware] the request that is being made
-
# @raise [VCR::Errors::NotSupportedError] if the fiber library cannot be loaded.
-
# @param filters [optional splat of #to_proc] one or more filters to apply.
-
# The objects provided will be converted to procs using `#to_proc`. If provided,
-
# the callback will only be invoked if these procs all return `true`.
-
# @note This method can only be used on ruby interpreters that support
-
# fibers (i.e. 1.9+). On 1.8 you can use separate `before_http_request` and
-
# `after_http_request` hooks.
-
# @note You _must_ call `request.proceed` or pass the request as a proc on to a
-
# method that yields to a block (i.e. `some_method(&request)`).
-
# @see #before_http_request
-
# @see #after_http_request
-
1
def around_http_request(*filters, &block)
-
unless VCR.fibers_available?
-
raise Errors::NotSupportedError.new \
-
"VCR::Configuration#around_http_request requires fibers, " +
-
"which are not available on your ruby intepreter."
-
end
-
-
fibers = {}
-
fiber_errors = {}
-
hook_allowed, hook_declaration = false, caller.first
-
before_http_request(*filters) do |request|
-
hook_allowed = true
-
start_new_fiber_for(request, fibers, fiber_errors, hook_declaration, block)
-
end
-
-
after_http_request(lambda { hook_allowed }) do |request, response|
-
fiber = fibers.delete(Thread.current)
-
resume_fiber(fiber, fiber_errors, response, hook_declaration)
-
end
-
end
-
-
# Configures RSpec to use a VCR cassette for any example
-
# tagged with `:vcr`.
-
1
def configure_rspec_metadata!
-
unless @rspec_metadata_configured
-
VCR::RSpec::Metadata.configure!
-
@rspec_metadata_configured = true
-
end
-
end
-
-
# An object to log debug output to.
-
#
-
# @overload debug_logger
-
# @return [#puts] the logger
-
# @overload debug_logger=(logger)
-
# @param logger [#puts] the logger
-
# @return [void]
-
# @example
-
# VCR.configure do |c|
-
# c.debug_logger = $stderr
-
# end
-
# @example
-
# VCR.configure do |c|
-
# c.debug_logger = File.open('vcr.log', 'w')
-
# end
-
1
attr_reader :debug_logger
-
# @private (documented above)
-
1
def debug_logger=(value)
-
1
@debug_logger = value
-
-
1
if value
-
@logger = Logger.new(value)
-
else
-
1
@logger = Logger::Null
-
end
-
end
-
-
# @private
-
# Logger object that provides logging APIs and helper methods.
-
1
attr_reader :logger
-
-
# Sets a callback that determines whether or not to base64 encode
-
# the bytes of a request or response body during serialization in
-
# order to preserve them exactly.
-
#
-
# @example
-
# VCR.configure do |c|
-
# c.preserve_exact_body_bytes do |http_message|
-
# http_message.body.encoding.name == 'ASCII-8BIT' ||
-
# !http_message.body.valid_encoding?
-
# end
-
# end
-
#
-
# @yield the callback
-
# @yieldparam http_message [#body, #headers] the `VCR::Request` or `VCR::Response` object being serialized
-
# @yieldparam cassette [VCR::Cassette] the cassette the http message belongs to
-
# @yieldreturn [Boolean] whether or not to preserve the exact bytes for the body of the given HTTP message
-
# @return [void]
-
# @see #preserve_exact_body_bytes_for?
-
# @note This is usually only necessary when the HTTP server returns a response
-
# with a non-standard encoding or with a body containing invalid bytes for the given
-
# encoding. Note that when you set this, and the block returns true, you sacrifice
-
# the human readability of the data in the cassette.
-
1
define_hook :preserve_exact_body_bytes
-
-
# @return [Boolean] whether or not the body of the given HTTP message should
-
# be base64 encoded during serialization in order to preserve the bytes exactly.
-
# @param http_message [#body, #headers] the `VCR::Request` or `VCR::Response` object being serialized
-
# @see #preserve_exact_body_bytes
-
1
def preserve_exact_body_bytes_for?(http_message)
-
invoke_hook(:preserve_exact_body_bytes, http_message, VCR.current_cassette).any?
-
end
-
-
1
private
-
-
1
def initialize
-
1
@allow_http_connections_when_no_cassette = nil
-
1
@rspec_metadata_configured = false
-
1
@default_cassette_options = {
-
:record => :once,
-
:match_requests_on => RequestMatcherRegistry::DEFAULT_MATCHERS,
-
:allow_unused_http_interactions => true,
-
:serialize_with => :yaml,
-
:persist_with => :file_system
-
}
-
-
1
self.uri_parser = URI
-
1
self.query_parser = CGI.method(:parse)
-
1
self.debug_logger = nil
-
-
1
register_built_in_hooks
-
end
-
-
1
def load_library_hook(hook)
-
1
file = "vcr/library_hooks/#{hook}"
-
1
require file
-
rescue LoadError => e
-
raise e unless e.message.include?(file) # in case FakeWeb/WebMock/etc itself is not available
-
raise ArgumentError.new("#{hook.inspect} is not a supported VCR HTTP library hook.")
-
end
-
-
1
def resume_fiber(fiber, fiber_errors, response, hook_declaration)
-
raise fiber_errors[Thread.current] if fiber_errors[Thread.current]
-
fiber.resume(response)
-
rescue FiberError => ex
-
raise Errors::AroundHTTPRequestHookError.new \
-
"Your around_http_request hook declared at #{hook_declaration}" \
-
" must call #proceed on the yielded request but did not. " \
-
"(actual error: #{ex.class}: #{ex.message})"
-
end
-
-
1
def create_fiber_for(fiber_errors, hook_declaration, proc)
-
current_thread = Thread.current
-
Fiber.new do |*args, &block|
-
begin
-
# JRuby Fiber runs in a separate thread, so we need to make this Fiber
-
# use the context of the calling thread
-
VCR.link_context(current_thread, Fiber.current) if RUBY_PLATFORM == 'java'
-
proc.call(*args, &block)
-
rescue StandardError => ex
-
# Fiber errors get swallowed, so we re-raise the error in the parent
-
# thread (see resume_fiber)
-
fiber_errors[current_thread] = ex
-
raise
-
ensure
-
VCR.unlink_context(Fiber.current) if RUBY_PLATFORM == 'java'
-
end
-
end
-
end
-
-
1
def start_new_fiber_for(request, fibers, fiber_errors, hook_declaration, proc)
-
fiber = create_fiber_for(fiber_errors, hook_declaration, proc)
-
fibers[Thread.current] = fiber
-
fiber.resume(Request::FiberAware.new(request))
-
end
-
-
1
def tag_filter_from(tag)
-
3
return lambda { true } unless tag
-
6
lambda { |_, cassette| cassette.tags.include?(tag) }
-
end
-
-
1
def request_filter_from(object)
-
return object unless object.is_a?(Symbol)
-
lambda { |arg| arg.send(object) }
-
end
-
-
1
def register_built_in_hooks
-
1
before_playback(:update_content_length_header) do |interaction|
-
interaction.response.update_content_length_header
-
end
-
-
1
before_record(:decode_compressed_response) do |interaction|
-
interaction.response.decompress if interaction.response.compressed?
-
end
-
-
1
preserve_exact_body_bytes do |http_message, cassette|
-
cassette && cassette.tags.include?(:preserve_exact_body_bytes)
-
end
-
end
-
-
1
def log_prefix
-
"[VCR::Configuration] "
-
end
-
-
# @private
-
1
define_hook :after_library_hooks_loaded
-
end
-
end
-
-
1
module VCR
-
# @deprecated Use #configure instead.
-
# @see #configure
-
1
def config
-
warn "WARNING: `VCR.config` is deprecated. Use VCR.configure instead."
-
configure { |c| yield c }
-
end
-
-
# @private
-
1
def self.const_missing(const)
-
return super unless const == :Config
-
warn "WARNING: `VCR::Config` is deprecated. Use VCR.configuration instead."
-
configuration
-
end
-
-
# @private
-
1
def Cassette.const_missing(const)
-
return super unless const == :MissingERBVariableError
-
warn "WARNING: `VCR::Cassette::MissingERBVariableError` is deprecated. Use `VCR::Errors::MissingERBVariableError` instead."
-
Errors::MissingERBVariableError
-
end
-
-
1
class Configuration
-
# @deprecated Use #hook_into instead.
-
# @see #hook_into
-
1
def stub_with(*adapters)
-
warn "WARNING: `VCR.configure { |c| c.stub_with ... }` is deprecated. Use `VCR.configure { |c| c.hook_into ... }` instead."
-
hook_into(*adapters)
-
end
-
end
-
-
# @private
-
1
module Deprecations
-
1
module Middleware
-
# @private
-
1
module Faraday
-
1
def initialize(*args)
-
if block_given?
-
Kernel.warn "WARNING: Passing a block to `VCR::Middleware::Faraday` is deprecated. \n" +
-
"As of VCR 2.0, you need to wrap faraday requests in VCR.use_cassette, just like with any other library hook."
-
end
-
end
-
end
-
end
-
end
-
-
1
module RSpec
-
# Contains macro methods to assist with VCR usage. These methods are
-
# intended to be used directly in an RSpec example group. To make these
-
# available in your RSpec example groups, extend the module in an individual
-
# example group, or configure RSpec to extend the module in all example groups.
-
#
-
# @example
-
# RSpec.configure do |c|
-
# c.extend VCR::RSpec::Macros
-
# end
-
#
-
1
module Macros
-
1
def self.extended(base)
-
Kernel.warn "WARNING: VCR::RSpec::Macros is deprecated. Use RSpec metadata options instead `:vcr => vcr_options`"
-
end
-
-
# Sets up a `before` and `after` hook that will insert and eject a
-
# cassette, respectively.
-
#
-
# @example
-
# describe "Some API Client" do
-
# use_vcr_cassette "some_api", :record => :new_episodes
-
# end
-
#
-
# @param [(optional) String] name the cassette name; it will be inferred by the example
-
# group descriptions if not given.
-
# @param [(optional) Hash] options the cassette options
-
# @deprecated Use RSpec metadata options
-
1
def use_vcr_cassette(*args)
-
options = args.last.is_a?(Hash) ? args.pop : {}
-
name = args.first || infer_cassette_name
-
-
before(:each) do
-
VCR.insert_cassette(name, options)
-
end
-
-
after(:each) do
-
VCR.eject_cassette
-
end
-
end
-
-
1
private
-
-
1
def infer_cassette_name
-
# RSpec 1 exposes #description_parts; use that if its available
-
return description_parts.join("/") if respond_to?(:description_parts)
-
-
# Otherwise use RSpec 2/3 metadata...
-
group_descriptions = []
-
klass = self
-
-
while klass.respond_to?(:metadata) && klass.metadata
-
group_descriptions << klass.metadata[:description] ||
-
klass.metadata[:example_group][:description]
-
klass = klass.superclass
-
end
-
-
group_descriptions.compact.reverse.join('/')
-
end
-
end
-
end
-
end
-
-
1
module VCR
-
# Namespace for VCR errors.
-
1
module Errors
-
# Base class for all VCR errors.
-
1
class Error < StandardError; end
-
-
# Error raised when VCR is turned off while a cassette is in use.
-
# @see VCR#turn_off!
-
# @see VCR#turned_off
-
1
class CassetteInUseError < Error; end
-
-
# Error raised when a VCR cassette is inserted while VCR is turned off.
-
# @see VCR#insert_cassette
-
# @see VCR#use_cassette
-
1
class TurnedOffError < Error; end
-
-
# Error raised when an cassette ERB template is rendered and a
-
# variable is missing.
-
# @see VCR#insert_cassette
-
# @see VCR#use_cassette
-
1
class MissingERBVariableError < Error; end
-
-
# Error raised when the version of one of the libraries that VCR hooks into
-
# is too low for VCR to support.
-
# @see VCR::Configuration#hook_into
-
1
class LibraryVersionTooLowError < Error; end
-
-
# Error raised when a request matcher is requested that is not registered.
-
1
class UnregisteredMatcherError < Error; end
-
-
# Error raised when a VCR 1.x cassette is used with VCR 2.
-
1
class InvalidCassetteFormatError < Error; end
-
-
# Error raised when an `around_http_request` hook is used improperly.
-
# @see VCR::Configuration#around_http_request
-
1
class AroundHTTPRequestHookError < Error; end
-
-
# Error raised when you attempt to use a VCR feature that is not
-
# supported on your ruby interpreter.
-
# @see VCR::Configuration#around_http_request
-
1
class NotSupportedError < Error; end
-
-
# Error raised when you ask VCR to decode a compressed response
-
# body but the content encoding isn't one of the known ones.
-
# @see VCR::Response#decompress
-
1
class UnknownContentEncodingError < Error; end
-
-
# Error raised when you eject a cassette before all previously
-
# recorded HTTP interactions are played back.
-
# @note Only applicable when :allow_episode_skipping is false.
-
# @see VCR::HTTPInteractionList#assert_no_unused_interactions!
-
1
class UnusedHTTPInteractionError < Error; end
-
-
# Error raised when you attempt to eject a cassette inserted by another
-
# thread.
-
1
class EjectLinkedCassetteError < Error; end
-
-
# Error raised when an HTTP request is made that VCR is unable to handle.
-
# @note VCR will raise this to force you to do something about the
-
# HTTP request. The idea is that you want to handle _every_ HTTP
-
# request in your test suite. The error message will give you
-
# suggestions for how to deal with the request.
-
1
class UnhandledHTTPRequestError < Error
-
# The HTTP request.
-
1
attr_reader :request
-
-
# Constructs the error.
-
#
-
# @param [VCR::Request] request the unhandled request.
-
1
def initialize(request)
-
@request = request
-
super construct_message
-
end
-
-
1
private
-
-
1
def relish_version_slug
-
@relish_version_slug ||= VCR.version.gsub(/\W/, '-')
-
end
-
-
1
def construct_message
-
["", "", "=" * 80,
-
"An HTTP request has been made that VCR does not know how to handle:",
-
"#{request_description}\n",
-
cassettes_description,
-
formatted_suggestions,
-
"=" * 80, "", ""].join("\n")
-
end
-
-
1
def current_cassettes
-
@cassettes ||= begin
-
cassettes = VCR.cassettes.to_a.reverse
-
-
begin
-
loop do
-
break unless VCR.eject_cassette
-
end
-
rescue EjectLinkedCassetteError
-
end
-
-
cassettes
-
end
-
end
-
-
1
def request_description
-
lines = []
-
-
lines << " #{request.method.to_s.upcase} #{request.uri}"
-
-
if match_request_on_body?
-
lines << " Body: #{request.body}"
-
end
-
-
lines.join("\n")
-
end
-
-
1
def match_request_on_body?
-
current_matchers.include?(:body)
-
end
-
-
1
def current_matchers
-
if current_cassettes.size > 0
-
current_cassettes.inject([]) do |memo, cassette|
-
memo | cassette.match_requests_on
-
end
-
else
-
VCR.configuration.default_cassette_options[:match_requests_on]
-
end
-
end
-
-
1
def cassettes_description
-
if current_cassettes.size > 0
-
[cassettes_list << "\n",
-
"Under the current configuration VCR can not find a suitable HTTP interaction",
-
"to replay and is prevented from recording new requests. There are a few ways",
-
"you can deal with this:\n"].join("\n")
-
else
-
["There is currently no cassette in use. There are a few ways",
-
"you can configure VCR to handle this request:\n"].join("\n")
-
end
-
end
-
-
1
def cassettes_list
-
lines = []
-
-
lines << if current_cassettes.size == 1
-
"VCR is currently using the following cassette:"
-
else
-
"VCR are currently using the following cassettes:"
-
end
-
-
lines = current_cassettes.inject(lines) do |memo, cassette|
-
memo.concat([
-
" - #{cassette.file}",
-
" - :record => #{cassette.record_mode.inspect}",
-
" - :match_requests_on => #{cassette.match_requests_on.inspect}"
-
])
-
end
-
-
lines.join("\n")
-
end
-
-
1
def formatted_suggestions
-
formatted_points, formatted_foot_notes = [], []
-
-
suggestions.each_with_index do |suggestion, index|
-
bullet_point, foot_note = suggestion.first, suggestion.last
-
formatted_points << format_bullet_point(bullet_point, index)
-
formatted_foot_notes << format_foot_note(foot_note, index)
-
end
-
-
[
-
formatted_points.join("\n"),
-
formatted_foot_notes.join("\n")
-
].join("\n\n")
-
end
-
-
1
def format_bullet_point(lines, index)
-
lines.first.insert(0, " * ")
-
lines.last << " [#{index + 1}]."
-
lines.join("\n ")
-
end
-
-
1
def format_foot_note(url, index)
-
"[#{index + 1}] #{url % relish_version_slug}"
-
end
-
-
# List of suggestions for how to configure VCR to handle the request.
-
1
ALL_SUGGESTIONS = {
-
:use_new_episodes => [
-
["You can use the :new_episodes record mode to allow VCR to",
-
"record this new request to the existing cassette"],
-
"https://www.relishapp.com/vcr/vcr/v/%s/docs/record-modes/new-episodes"
-
],
-
-
:delete_cassette_for_once => [
-
["The current record mode (:once) does not allow new requests to be recorded",
-
"to a previously recorded cassette. You can delete the cassette file and re-run",
-
"your tests to allow the cassette to be recorded with this request"],
-
"https://www.relishapp.com/vcr/vcr/v/%s/docs/record-modes/once"
-
],
-
-
:deal_with_none => [
-
["The current record mode (:none) does not allow requests to be recorded. You",
-
"can temporarily change the record mode to :once, delete the cassette file ",
-
"and re-run your tests to allow the cassette to be recorded with this request"],
-
"https://www.relishapp.com/vcr/vcr/v/%s/docs/record-modes/none"
-
],
-
-
:use_a_cassette => [
-
["If you want VCR to record this request and play it back during future test",
-
"runs, you should wrap your test (or this portion of your test) in a",
-
"`VCR.use_cassette` block"],
-
"https://www.relishapp.com/vcr/vcr/v/%s/docs/getting-started"
-
],
-
-
:allow_http_connections_when_no_cassette => [
-
["If you only want VCR to handle requests made while a cassette is in use,",
-
"configure `allow_http_connections_when_no_cassette = true`. VCR will",
-
"ignore this request since it is made when there is no cassette"],
-
"https://www.relishapp.com/vcr/vcr/v/%s/docs/configuration/allow-http-connections-when-no-cassette"
-
],
-
-
:ignore_request => [
-
["If you want VCR to ignore this request (and others like it), you can",
-
"set an `ignore_request` callback"],
-
"https://www.relishapp.com/vcr/vcr/v/%s/docs/configuration/ignore-request"
-
],
-
-
:allow_playback_repeats => [
-
["The cassette contains an HTTP interaction that matches this request,",
-
"but it has already been played back. If you wish to allow a single HTTP",
-
"interaction to be played back multiple times, set the `:allow_playback_repeats`",
-
"cassette option"],
-
"https://www.relishapp.com/vcr/vcr/v/%s/docs/request-matching/playback-repeats"
-
],
-
-
:match_requests_on => [
-
["The cassette contains %s not been",
-
"played back. If your request is non-deterministic, you may need to",
-
"change your :match_requests_on cassette option to be more lenient",
-
"or use a custom request matcher to allow it to match"],
-
"https://www.relishapp.com/vcr/vcr/v/%s/docs/request-matching"
-
],
-
-
:try_debug_logger => [
-
["If you're surprised VCR is raising this error",
-
"and want insight about how VCR attempted to handle the request,",
-
"you can use the debug_logger configuration option to log more details"],
-
"https://www.relishapp.com/vcr/vcr/v/%s/docs/configuration/debug-logging"
-
]
-
}
-
-
1
def suggestion_for(key)
-
bullet_point_lines, url = ALL_SUGGESTIONS[key]
-
bullet_point_lines = bullet_point_lines.map(&:dup)
-
url = url.dup
-
[bullet_point_lines, url]
-
end
-
-
1
def suggestions
-
return no_cassette_suggestions if current_cassettes.size == 0
-
-
[:try_debug_logger, :use_new_episodes, :ignore_request].tap do |suggestions|
-
suggestions.push(*record_mode_suggestion)
-
suggestions << :allow_playback_repeats if has_used_interaction_matching?
-
suggestions.map! { |k| suggestion_for(k) }
-
suggestions.push(*match_requests_on_suggestion)
-
end
-
end
-
-
1
def no_cassette_suggestions
-
[:try_debug_logger, :use_a_cassette, :allow_http_connections_when_no_cassette, :ignore_request].map do |key|
-
suggestion_for(key)
-
end
-
end
-
-
1
def record_mode_suggestion
-
record_modes = current_cassettes.map(&:record_mode)
-
-
if record_modes.all?{|r| r == :none }
-
[:deal_with_none]
-
elsif record_modes.all?{|r| r == :once }
-
[:delete_cassette_for_once]
-
else
-
[]
-
end
-
end
-
-
1
def has_used_interaction_matching?
-
current_cassettes.any?{|c| c.http_interactions.has_used_interaction_matching?(request) }
-
end
-
-
1
def match_requests_on_suggestion
-
num_remaining_interactions = current_cassettes.inject(0) { |sum, c|
-
sum + c.http_interactions.remaining_unused_interaction_count
-
}
-
-
return [] if num_remaining_interactions.zero?
-
-
interaction_description = if num_remaining_interactions == 1
-
"1 HTTP interaction that has"
-
else
-
"#{num_remaining_interactions} HTTP interactions that have"
-
end
-
-
description_lines, link = suggestion_for(:match_requests_on)
-
description_lines[0] = description_lines[0] % interaction_description
-
[[description_lines, link]]
-
end
-
-
end
-
end
-
end
-
-
1
module VCR
-
# @private
-
1
class LibraryHooks
-
1
attr_accessor :exclusive_hook
-
-
1
def disabled?(hook)
-
16
![nil, hook].include?(exclusive_hook)
-
end
-
-
1
def exclusively_enabled(hook)
-
self.exclusive_hook = hook
-
yield
-
ensure
-
self.exclusive_hook = nil
-
end
-
end
-
end
-
-
1
require 'vcr/util/version_checker'
-
1
require 'vcr/request_handler'
-
1
require 'webmock'
-
-
1
VCR::VersionChecker.new('WebMock', WebMock.version, '1.8.0').check_version!
-
-
1
module VCR
-
1
class LibraryHooks
-
# @private
-
1
module WebMock
-
1
extend self
-
-
1
attr_accessor :global_hook_disabled
-
1
alias global_hook_disabled? global_hook_disabled
-
-
1
def with_global_hook_disabled
-
4
self.global_hook_disabled = true
-
-
4
begin
-
4
yield
-
ensure
-
4
self.global_hook_disabled = false
-
end
-
end
-
-
# @private
-
1
module Helpers
-
1
def vcr_request_for(webmock_request)
-
4
VCR::Request.new \
-
webmock_request.method,
-
webmock_request.uri.to_s,
-
webmock_request.body,
-
request_headers_for(webmock_request)
-
end
-
-
# @private
-
1
def vcr_response_for(webmock_response)
-
4
VCR::Response.new \
-
VCR::ResponseStatus.new(*webmock_response.status),
-
webmock_response.headers,
-
webmock_response.body,
-
nil
-
end
-
-
1
if defined?(::Excon)
-
# @private
-
def request_headers_for(webmock_request)
-
return nil unless webmock_request.headers
-
-
# WebMock hooks deeply into a Excon at a place where it manually adds a "Host"
-
# header, but this isn't a header we actually care to store...
-
webmock_request.headers.dup.tap do |headers|
-
headers.delete("Host")
-
end
-
end
-
else
-
# @private
-
1
def request_headers_for(webmock_request)
-
4
webmock_request.headers
-
end
-
end
-
-
1
def typed_request_for(webmock_request, remove = false)
-
24
if webmock_request.instance_variables.find { |v| v.to_sym == :@__typed_vcr_request }
-
4
meth = remove ? :remove_instance_variable : :instance_variable_get
-
4
return webmock_request.send(meth, :@__typed_vcr_request)
-
end
-
-
warn <<-EOS.gsub(/^\s+\|/, '')
-
|WARNING: There appears to be a bug in WebMock's after_request hook
-
| and VCR is attempting to work around it. Some VCR features
-
| may not work properly.
-
EOS
-
-
Request::Typed.new(vcr_request_for(webmock_request), :unknown)
-
end
-
end
-
-
1
class RequestHandler < ::VCR::RequestHandler
-
1
include Helpers
-
-
1
attr_reader :request
-
1
def initialize(request)
-
4
@request = request
-
end
-
-
1
private
-
-
1
def externally_stubbed?
-
# prevent infinite recursion...
-
4
VCR::LibraryHooks::WebMock.with_global_hook_disabled do
-
4
::WebMock.registered_request?(request)
-
end
-
end
-
-
1
def set_typed_request_for_after_hook(*args)
-
4
super
-
4
request.instance_variable_set(:@__typed_vcr_request, @after_hook_typed_request)
-
end
-
-
1
def vcr_request
-
20
@vcr_request ||= vcr_request_for(request)
-
end
-
-
1
def on_externally_stubbed_request
-
# nil allows WebMock to handle the request
-
nil
-
end
-
-
1
def on_unhandled_request
-
invoke_after_request_hook(nil)
-
super
-
end
-
-
1
def on_stubbed_by_vcr_request
-
{
-
:body => stubbed_response.body.dup, # Excon mutates the body, so we must dup it :-(
-
:status => [stubbed_response.status.code.to_i, stubbed_response.status.message],
-
:headers => stubbed_response.headers
-
4
}
-
end
-
end
-
-
1
extend Helpers
-
-
1
::WebMock.globally_stub_request do |req|
-
8
global_hook_disabled? ? nil : RequestHandler.new(req).handle
-
end
-
-
1
::WebMock.after_request(:real_requests_only => true) do |request, response|
-
unless VCR.library_hooks.disabled?(:webmock)
-
http_interaction = VCR::HTTPInteraction.new \
-
typed_request_for(request), vcr_response_for(response)
-
-
VCR.record_http_interaction(http_interaction)
-
end
-
end
-
-
1
::WebMock.after_request do |request, response|
-
4
unless VCR.library_hooks.disabled?(:webmock)
-
4
VCR.configuration.invoke_hook \
-
:after_http_request,
-
typed_request_for(request, :remove),
-
vcr_response_for(response)
-
end
-
end
-
end
-
end
-
end
-
-
# @private
-
1
module WebMock
-
class << self
-
# ensure HTTP requests are always allowed; VCR takes care of disallowing
-
# them at the appropriate times in its hook
-
1
def net_connect_allowed_with_vcr?(*args)
-
VCR.turned_on? ? true : net_connect_allowed_without_vcr?(*args)
-
end
-
-
1
alias net_connect_allowed_without_vcr? net_connect_allowed?
-
1
alias net_connect_allowed? net_connect_allowed_with_vcr?
-
1
end unless respond_to?(:net_connect_allowed_with_vcr?)
-
end
-
-
1
require 'delegate'
-
1
require 'vcr/errors'
-
-
1
module VCR
-
# A Cassette wrapper for linking cassettes from another thread
-
1
class LinkedCassette < SimpleDelegator
-
# An enumerable lazily wrapping a list of cassettes that a context is using
-
1
class CassetteList
-
1
include Enumerable
-
-
# Creates a new list of context-owned cassettes and linked cassettes
-
# @param [Array] context-owned cassettes
-
# @param [Array] context-unowned (linked) cassettes
-
1
def initialize(cassettes, linked_cassettes)
-
28
@cassettes = cassettes
-
28
@linked_cassettes = linked_cassettes
-
end
-
-
# Yields linked cassettes first, and then context-owned cassettes
-
1
def each
-
4
@linked_cassettes.each do |cassette|
-
yield wrap(cassette)
-
end
-
-
4
@cassettes.each do |cassette|
-
yield cassette
-
end
-
end
-
-
# Provide last implementation, which is not provided by Enumerable
-
1
def last
-
24
cassette = @cassettes.last
-
24
return cassette if cassette
-
-
4
cassette = @linked_cassettes.last
-
4
wrap(cassette) if cassette
-
end
-
-
# Provide size implementation, which is not provided by Enumerable
-
1
def size
-
@cassettes.size + @linked_cassettes.size
-
end
-
-
1
protected
-
1
def wrap(cassette)
-
if cassette.linked?
-
cassette
-
else
-
LinkedCassette.new(cassette)
-
end
-
end
-
end
-
-
# Create a new CassetteList
-
# @param [Array] context-owned cassettes
-
# @param [Array] context-unowned (linked) cassettes
-
1
def self.list(cassettes, linked_cassettes)
-
28
CassetteList.new(cassettes, linked_cassettes)
-
end
-
-
# Prevents cassette ejection by raising EjectLinkedCassetteError
-
1
def eject(*args)
-
raise Errors::EjectLinkedCassetteError,
-
"cannot eject a cassette inserted by a parent thread"
-
end
-
-
# @return [Boolean] true
-
1
def linked?
-
true
-
end
-
end
-
end
-
1
module VCR
-
# @private
-
1
class RequestHandler
-
1
include Logger::Mixin
-
-
1
def handle
-
4
log "Handling request: #{request_summary} (disabled: #{disabled?})"
-
4
invoke_before_request_hook
-
-
4
req_type = request_type(:consume_stub)
-
-
4
log "Identified request type (#{req_type}) for #{request_summary}"
-
-
# The before_request hook can change the type of request
-
# (i.e. by inserting a cassette), so we need to query the
-
# request type again.
-
#
-
# Likewise, the main handler logic can modify what
-
# #request_type would return (i.e. when a response stub is
-
# used), so we need to store the request type for the
-
# after_request hook.
-
4
set_typed_request_for_after_hook(req_type)
-
-
4
send "on_#{req_type}_request"
-
end
-
-
1
private
-
-
1
def set_typed_request_for_after_hook(request_type)
-
4
@after_hook_typed_request = Request::Typed.new(vcr_request, request_type)
-
end
-
-
1
def request_type(consume_stub = false)
-
case
-
when externally_stubbed? then :externally_stubbed
-
when should_ignore? then :ignored
-
4
when has_response_stub?(consume_stub) then :stubbed_by_vcr
-
when VCR.real_http_connections_allowed? then :recordable
-
else :unhandled
-
4
end
-
end
-
-
1
def invoke_before_request_hook
-
4
return if disabled? || !VCR.configuration.has_hooks_for?(:before_http_request)
-
typed_request = Request::Typed.new(vcr_request, request_type)
-
VCR.configuration.invoke_hook(:before_http_request, typed_request)
-
end
-
-
1
def invoke_after_request_hook(vcr_response)
-
return if disabled?
-
VCR.configuration.invoke_hook(:after_http_request, @after_hook_typed_request, vcr_response)
-
end
-
-
1
def externally_stubbed?
-
false
-
end
-
-
1
def should_ignore?
-
4
disabled? || VCR.request_ignorer.ignore?(vcr_request)
-
end
-
-
1
def disabled?
-
12
VCR.library_hooks.disabled?(library_name)
-
end
-
-
1
def has_response_stub?(consume_stub)
-
4
if consume_stub
-
4
stubbed_response
-
else
-
VCR.http_interactions.has_interaction_matching?(vcr_request)
-
end
-
end
-
-
1
def stubbed_response
-
20
@stubbed_response ||= VCR.http_interactions.response_for(vcr_request)
-
end
-
-
1
def library_name
-
# extracts `:typhoeus` from `VCR::LibraryHooks::Typhoeus::RequestHandler`
-
20
@library_name ||= self.class.name.split('::')[-2].downcase.to_sym
-
end
-
-
# Subclasses can implement these
-
1
def on_externally_stubbed_request
-
end
-
-
1
def on_ignored_request
-
end
-
-
1
def on_stubbed_by_vcr_request
-
end
-
-
1
def on_recordable_request
-
end
-
-
1
def on_unhandled_request
-
raise VCR::Errors::UnhandledHTTPRequestError.new(vcr_request)
-
end
-
-
1
def request_summary
-
8
request_matchers = if cass = VCR.current_cassette
-
8
cass.match_requests_on
-
else
-
VCR.configuration.default_cassette_options[:match_requests_on]
-
end
-
-
8
super(vcr_request, request_matchers)
-
end
-
-
1
def log_prefix
-
8
"[#{library_name}] "
-
end
-
end
-
end
-
1
require 'set'
-
1
require 'vcr/util/hooks'
-
-
1
module VCR
-
# @private
-
1
class RequestIgnorer
-
1
include VCR::Hooks
-
-
1
define_hook :ignore_request
-
-
1
LOCALHOST_ALIASES = %w( localhost 127.0.0.1 0.0.0.0 )
-
-
1
def initialize
-
1
ignore_request do |request|
-
8
host = request.parsed_uri.host
-
8
ignored_hosts.include?(host)
-
end
-
end
-
-
1
def ignore_localhost=(value)
-
if value
-
ignore_hosts(*LOCALHOST_ALIASES)
-
else
-
ignored_hosts.reject! { |h| LOCALHOST_ALIASES.include?(h) }
-
end
-
end
-
-
1
def ignore_hosts(*hosts)
-
ignored_hosts.merge(hosts)
-
end
-
-
1
def ignore?(request)
-
8
invoke_hook(:ignore_request, request).any?
-
end
-
-
1
private
-
-
1
def ignored_hosts
-
8
@ignored_hosts ||= Set.new
-
end
-
end
-
end
-
-
1
require 'vcr/errors'
-
-
1
module VCR
-
# Keeps track of the different request matchers.
-
1
class RequestMatcherRegistry
-
-
# The default request matchers used for any cassette that does not
-
# specify request matchers.
-
1
DEFAULT_MATCHERS = [:method, :uri]
-
-
# @private
-
1
class Matcher < Struct.new(:callable)
-
1
def matches?(request_1, request_2)
-
8
callable.call(request_1, request_2)
-
end
-
end
-
-
# @private
-
1
class URIWithoutParamsMatcher < Struct.new(:params_to_ignore)
-
1
def partial_uri_from(request)
-
8
request.parsed_uri.tap do |uri|
-
8
return uri unless uri.query # ignore uris without params, e.g. "http://example.com/"
-
-
8
uri.query = uri.query.split('&').tap { |params|
-
8
params.map! do |p|
-
34
key, value = p.split('=')
-
34
key.gsub!(/\[\]\z/, '') # handle params like tag[]=
-
34
[key, value]
-
end
-
-
42
params.reject! { |p| params_to_ignore.include?(p.first) }
-
26
params.map! { |p| p.join('=') }
-
}.join('&')
-
-
8
uri.query = nil if uri.query.empty?
-
end
-
end
-
-
1
def call(request_1, request_2)
-
4
partial_uri_from(request_1) == partial_uri_from(request_2)
-
end
-
-
1
def to_proc
-
lambda { |r1, r2| call(r1, r2) }
-
end
-
end
-
-
# @private
-
1
def initialize
-
1
@registry = {}
-
1
register_built_ins
-
end
-
-
# @private
-
1
def register(name, &block)
-
8
if @registry.has_key?(name)
-
warn "WARNING: There is already a VCR request matcher registered for #{name.inspect}. Overriding it."
-
end
-
-
8
@registry[name] = Matcher.new(block)
-
end
-
-
# @private
-
1
def [](matcher)
-
8
@registry.fetch(matcher) do
-
4
matcher.respond_to?(:call) ?
-
Matcher.new(matcher) :
-
raise_unregistered_matcher_error(matcher)
-
end
-
end
-
-
# Builds a dynamic request matcher that matches on a URI while ignoring the
-
# named query parameters. This is useful for dealing with non-deterministic
-
# URIs (i.e. that have a timestamp or request signature parameter).
-
#
-
# @example
-
# without_timestamp = VCR.request_matchers.uri_without_param(:timestamp)
-
#
-
# # use it directly...
-
# VCR.use_cassette('example', :match_requests_on => [:method, without_timestamp]) { }
-
#
-
# # ...or register it as a named matcher
-
# VCR.configure do |c|
-
# c.register_request_matcher(:uri_without_timestamp, &without_timestamp)
-
# end
-
#
-
# VCR.use_cassette('example', :match_requests_on => [:method, :uri_without_timestamp]) { }
-
#
-
# @param ignores [Array<#to_s>] The names of the query parameters to ignore
-
# @return [#call] the request matcher
-
1
def uri_without_params(*ignores)
-
1
uri_without_param_matchers[ignores]
-
end
-
1
alias uri_without_param uri_without_params
-
-
1
private
-
-
1
def uri_without_param_matchers
-
@uri_without_param_matchers ||= Hash.new do |hash, params|
-
1
params = params.map(&:to_s)
-
1
hash[params] = URIWithoutParamsMatcher.new(params)
-
1
end
-
end
-
-
1
def raise_unregistered_matcher_error(name)
-
raise Errors::UnregisteredMatcherError.new \
-
"There is no matcher registered for #{name.inspect}. " +
-
"Did you mean one of #{@registry.keys.map(&:inspect).join(', ')}?"
-
end
-
-
1
def register_built_ins
-
5
register(:method) { |r1, r2| r1.method == r2.method }
-
1
register(:uri) { |r1, r2| r1.uri == r2.uri }
-
1
register(:body) { |r1, r2| r1.body == r2.body }
-
1
register(:headers) { |r1, r2| r1.headers == r2.headers }
-
-
1
register(:host) do |r1, r2|
-
r1.parsed_uri.host == r2.parsed_uri.host
-
end
-
1
register(:path) do |r1, r2|
-
r1.parsed_uri.path == r2.parsed_uri.path
-
end
-
-
1
register(:query) do |r1, r2|
-
VCR.configuration.query_parser.call(r1.parsed_uri.query.to_s) ==
-
VCR.configuration.query_parser.call(r2.parsed_uri.query.to_s)
-
end
-
-
1
try_to_register_body_as_json
-
end
-
-
1
def try_to_register_body_as_json
-
1
begin
-
1
require 'json'
-
rescue LoadError
-
return
-
end
-
-
1
register(:body_as_json) do |r1, r2|
-
begin
-
JSON.parse(r1.body) == JSON.parse(r2.body)
-
rescue JSON::ParserError
-
false
-
end
-
end
-
end
-
end
-
end
-
-
1
require 'base64'
-
1
require 'delegate'
-
1
require 'time'
-
-
1
module VCR
-
# @private
-
1
module Normalizers
-
# @private
-
1
module Body
-
1
def self.included(klass)
-
2
klass.extend ClassMethods
-
end
-
-
# @private
-
1
module ClassMethods
-
1
def body_from(hash_or_string)
-
8
return hash_or_string unless hash_or_string.is_a?(Hash)
-
8
hash = hash_or_string
-
-
8
if hash.has_key?('base64_string')
-
string = Base64.decode64(hash['base64_string'])
-
force_encode_string(string, hash['encoding'])
-
else
-
8
try_encode_string(hash['string'], hash['encoding'])
-
end
-
end
-
-
1
if "".respond_to?(:encoding)
-
1
def force_encode_string(string, encoding)
-
return string unless encoding
-
string.force_encoding(encoding)
-
end
-
-
1
def try_encode_string(string, encoding)
-
8
return string if encoding.nil? || string.encoding.name == encoding
-
-
# ASCII-8BIT just means binary, so encoding to it is nonsensical
-
# and yet "\u00f6".encode("ASCII-8BIT") raises an error.
-
# Instead, we'll force encode it (essentially just tagging it as binary)
-
4
return string.force_encoding(encoding) if encoding == "ASCII-8BIT"
-
-
4
string.encode(encoding)
-
rescue EncodingError => e
-
struct_type = name.split('::').last.downcase
-
warn "VCR: got `#{e.class.name}: #{e.message}` while trying to encode the #{string.encoding.name} " +
-
"#{struct_type} body to the original body encoding (#{encoding}). Consider using the " +
-
"`:preserve_exact_body_bytes` option to work around this."
-
return string
-
end
-
else
-
def force_encode_string(string, encoding)
-
string
-
end
-
-
def try_encode_string(string, encoding)
-
string
-
end
-
end
-
end
-
-
1
def initialize(*args)
-
16
super
-
-
16
if body && !body.is_a?(String)
-
raise ArgumentError, "#{self.class} initialized with an invalid body: #{body.inspect}."
-
end
-
-
# Ensure that the body is a raw string, in case the string instance
-
# has been subclassed or extended with additional instance variables
-
# or attributes, so that it is serialized to YAML as a raw string.
-
# This is needed for rest-client. See this ticket for more info:
-
# http://github.com/myronmarston/vcr/issues/4
-
16
self.body = String.new(body.to_s)
-
end
-
-
1
private
-
-
1
def serializable_body
-
# Ensure it's just a string, and not a string with some
-
# extra state, as such strings serialize to YAML with
-
# all the extra state.
-
body = String.new(self.body.to_s)
-
-
if VCR.configuration.preserve_exact_body_bytes_for?(self)
-
base_body_hash(body).merge('base64_string' => Base64.encode64(body))
-
else
-
base_body_hash(body).merge('string' => body)
-
end
-
end
-
-
1
if ''.respond_to?(:encoding)
-
1
def base_body_hash(body)
-
{ 'encoding' => body.encoding.name }
-
end
-
else
-
def base_body_hash(body)
-
{ }
-
end
-
end
-
end
-
-
# @private
-
1
module Header
-
1
def initialize(*args)
-
16
super
-
16
normalize_headers
-
end
-
-
1
private
-
-
1
def normalize_headers
-
16
new_headers = {}
-
16
@normalized_header_keys = Hash.new {|h,k| k }
-
-
headers.each do |k, v|
-
62
val_array = case v
-
31
when Array then v
-
when nil then []
-
31
else [v]
-
end
-
-
62
new_headers[String.new(k)] = convert_to_raw_strings(val_array)
-
62
@normalized_header_keys[k.downcase] = k
-
16
end if headers
-
-
16
self.headers = new_headers
-
end
-
-
1
def header_key(key)
-
key = @normalized_header_keys[key.downcase]
-
key if headers.has_key? key
-
end
-
-
1
def get_header(key)
-
key = header_key(key)
-
headers[key] if key
-
end
-
-
1
def edit_header(key, value = nil)
-
if key = header_key(key)
-
value ||= yield headers[key]
-
headers[key] = Array(value)
-
end
-
end
-
-
1
def delete_header(key)
-
if key = header_key(key)
-
@normalized_header_keys.delete key.downcase
-
headers.delete key
-
end
-
end
-
-
1
def convert_to_raw_strings(array)
-
# Ensure the values are raw strings.
-
# Apparently for Paperclip uploads to S3, headers
-
# get serialized with some extra stuff which leads
-
# to a seg fault. See this issue for more info:
-
# https://github.com/myronmarston/vcr/issues#issue/39
-
62
array.map do |v|
-
62
case v
-
62
when String; String.new(v)
-
when Array; convert_to_raw_strings(v)
-
else v
-
end
-
end
-
end
-
end
-
end
-
-
# @private
-
1
module OrderedHashSerializer
-
1
def each
-
@ordered_keys.each do |key|
-
yield key, self[key] if has_key?(key)
-
end
-
end
-
-
1
if RUBY_VERSION.to_f > 1.8
-
# 1.9+ hashes are already ordered.
-
1
def self.apply_to(*args); end
-
else
-
def self.apply_to(hash, keys)
-
hash.instance_variable_set(:@ordered_keys, keys)
-
hash.extend self
-
end
-
end
-
end
-
-
# The request of an {HTTPInteraction}.
-
#
-
# @attr [Symbol] method the HTTP method (i.e. :head, :options, :get, :post, :put, :patch or :delete)
-
# @attr [String] uri the request URI
-
# @attr [String, nil] body the request body
-
# @attr [Hash{String => Array<String>}] headers the request headers
-
1
class Request < Struct.new(:method, :uri, :body, :headers)
-
1
include Normalizers::Header
-
1
include Normalizers::Body
-
-
1
def initialize(*args)
-
8
skip_port_stripping = false
-
8
if args.last == :skip_port_stripping
-
4
skip_port_stripping = true
-
4
args.pop
-
end
-
-
8
super(*args)
-
8
self.method = self.method.to_s.downcase.to_sym if self.method
-
8
self.uri = without_standard_port(self.uri) unless skip_port_stripping
-
end
-
-
# Builds a serializable hash from the request data.
-
#
-
# @return [Hash] hash that represents this request and can be easily
-
# serialized.
-
# @see Request.from_hash
-
1
def to_hash
-
{
-
'method' => method.to_s,
-
'uri' => uri,
-
'body' => serializable_body,
-
'headers' => headers
-
}.tap { |h| OrderedHashSerializer.apply_to(h, members) }
-
end
-
-
# Constructs a new instance from a hash.
-
#
-
# @param [Hash] hash the hash to use to construct the instance.
-
# @return [Request] the request
-
1
def self.from_hash(hash)
-
4
method = hash['method']
-
4
method &&= method.to_sym
-
4
new method,
-
hash['uri'],
-
body_from(hash['body']),
-
hash['headers'],
-
:skip_port_stripping
-
end
-
-
# Parses the URI using the configured `uri_parser`.
-
#
-
# @return [#schema, #host, #port, #path, #query] A parsed URI object.
-
1
def parsed_uri
-
20
VCR.configuration.uri_parser.parse(uri)
-
end
-
-
1
@@object_method = Object.instance_method(:method)
-
1
def method(*args)
-
24
return super if args.empty?
-
@@object_method.bind(self).call(*args)
-
end
-
-
# Decorates a {Request} with its current type.
-
1
class Typed < DelegateClass(self)
-
# @return [Symbol] One of `:ignored`, `:stubbed`, `:recordable` or `:unhandled`.
-
1
attr_reader :type
-
-
# @param [Request] request the request
-
# @param [Symbol] type the type. Should be one of `:ignored`, `:stubbed`, `:recordable` or `:unhandled`.
-
1
def initialize(request, type)
-
4
@type = type
-
4
super(request)
-
end
-
-
# @return [Boolean] whether or not this request is being ignored
-
1
def ignored?
-
type == :ignored
-
end
-
-
# @return [Boolean] whether or not this request is being stubbed by VCR
-
# @see #externally_stubbed?
-
# @see #stubbed?
-
1
def stubbed_by_vcr?
-
type == :stubbed_by_vcr
-
end
-
-
# @return [Boolean] whether or not this request is being stubbed by an
-
# external library (such as WebMock or FakeWeb).
-
# @see #stubbed_by_vcr?
-
# @see #stubbed?
-
1
def externally_stubbed?
-
type == :externally_stubbed
-
end
-
-
# @return [Boolean] whether or not this request will be recorded.
-
1
def recordable?
-
type == :recordable
-
end
-
-
# @return [Boolean] whether or not VCR knows how to handle this request.
-
1
def unhandled?
-
type == :unhandled
-
end
-
-
# @return [Boolean] whether or not this request will be made for real.
-
# @note VCR allows `:ignored` and `:recordable` requests to be made for real.
-
1
def real?
-
ignored? || recordable?
-
end
-
-
# @return [Boolean] whether or not this request will be stubbed.
-
# It may be stubbed by an external library or by VCR.
-
# @see #stubbed_by_vcr?
-
# @see #externally_stubbed?
-
1
def stubbed?
-
stubbed_by_vcr? || externally_stubbed?
-
end
-
-
1
undef method
-
end
-
-
# Provides fiber-awareness for the {VCR::Configuration#around_http_request} hook.
-
1
class FiberAware < DelegateClass(Typed)
-
# Yields the fiber so the request can proceed.
-
#
-
# @return [VCR::Response] the response from the request
-
1
def proceed
-
Fiber.yield
-
end
-
-
# Builds a proc that allows the request to proceed when called.
-
# This allows you to treat the request as a proc and pass it on
-
# to a method that yields (at which point the request will proceed).
-
#
-
# @return [Proc] the proc
-
1
def to_proc
-
lambda { proceed }
-
end
-
-
1
undef method
-
end
-
-
1
private
-
-
1
def without_standard_port(uri)
-
4
return uri if uri.nil?
-
4
u = parsed_uri
-
4
return uri unless [['http', 80], ['https', 443]].include?([u.scheme, u.port])
-
4
u.port = nil
-
4
u.to_s
-
end
-
end
-
-
# The response of an {HTTPInteraction}.
-
#
-
# @attr [ResponseStatus] status the status of the response
-
# @attr [Hash{String => Array<String>}] headers the response headers
-
# @attr [String] body the response body
-
# @attr [nil, String] http_version the HTTP version
-
# @attr [Hash] adapter_metadata Additional metadata used by a specific VCR adapter.
-
1
class Response < Struct.new(:status, :headers, :body, :http_version, :adapter_metadata)
-
1
include Normalizers::Header
-
1
include Normalizers::Body
-
-
1
def initialize(*args)
-
8
super(*args)
-
8
self.adapter_metadata ||= {}
-
end
-
-
# Builds a serializable hash from the response data.
-
#
-
# @return [Hash] hash that represents this response
-
# and can be easily serialized.
-
# @see Response.from_hash
-
1
def to_hash
-
{
-
'status' => status.to_hash,
-
'headers' => headers,
-
'body' => serializable_body,
-
'http_version' => http_version
-
}.tap do |hash|
-
hash['adapter_metadata'] = adapter_metadata unless adapter_metadata.empty?
-
OrderedHashSerializer.apply_to(hash, members)
-
end
-
end
-
-
# Constructs a new instance from a hash.
-
#
-
# @param [Hash] hash the hash to use to construct the instance.
-
# @return [Response] the response
-
1
def self.from_hash(hash)
-
4
new ResponseStatus.from_hash(hash.fetch('status', {})),
-
hash['headers'],
-
body_from(hash['body']),
-
hash['http_version'],
-
hash['adapter_metadata']
-
end
-
-
# Updates the Content-Length response header so that it is
-
# accurate for the response body.
-
1
def update_content_length_header
-
edit_header('Content-Length') { body ? body.bytesize.to_s : '0' }
-
end
-
-
# The type of encoding.
-
#
-
# @return [String] encoding type
-
1
def content_encoding
-
enc = get_header('Content-Encoding') and enc.first
-
end
-
-
# Checks if the type of encoding is one of "gzip" or "deflate".
-
1
def compressed?
-
%w[ gzip deflate ].include? content_encoding
-
end
-
-
# Decodes the compressed body and deletes evidence that it was ever compressed.
-
#
-
# @return self
-
# @raise [VCR::Errors::UnknownContentEncodingError] if the content encoding
-
# is not a known encoding.
-
1
def decompress
-
self.class.decompress(body, content_encoding) { |new_body|
-
self.body = new_body
-
update_content_length_header
-
delete_header('Content-Encoding')
-
}
-
return self
-
end
-
-
1
begin
-
1
require 'zlib'
-
1
require 'stringio'
-
1
HAVE_ZLIB = true
-
rescue LoadError
-
HAVE_ZLIB = false
-
end
-
-
# Decode string compressed with gzip or deflate
-
#
-
# @raise [VCR::Errors::UnknownContentEncodingError] if the content encoding
-
# is not a known encoding.
-
1
def self.decompress(body, type)
-
unless HAVE_ZLIB
-
warn "VCR: cannot decompress response; Zlib not available"
-
return
-
end
-
-
case type
-
when 'gzip'
-
args = [StringIO.new(body)]
-
args << { :encoding => 'ASCII-8BIT' } if ''.respond_to?(:encoding)
-
yield Zlib::GzipReader.new(*args).read
-
when 'deflate'
-
yield Zlib::Inflate.inflate(body)
-
when 'identity', NilClass
-
return
-
else
-
raise Errors::UnknownContentEncodingError, "unknown content encoding: #{type}"
-
end
-
end
-
end
-
-
# The response status of an {HTTPInteraction}.
-
#
-
# @attr [Integer] code the HTTP status code
-
# @attr [String] message the HTTP status message (e.g. "OK" for a status of 200)
-
1
class ResponseStatus < Struct.new(:code, :message)
-
# Builds a serializable hash from the response status data.
-
#
-
# @return [Hash] hash that represents this response status
-
# and can be easily serialized.
-
# @see ResponseStatus.from_hash
-
1
def to_hash
-
{
-
'code' => code, 'message' => message
-
}.tap { |h| OrderedHashSerializer.apply_to(h, members) }
-
end
-
-
# Constructs a new instance from a hash.
-
#
-
# @param [Hash] hash the hash to use to construct the instance.
-
# @return [ResponseStatus] the response status
-
1
def self.from_hash(hash)
-
4
new hash['code'], hash['message']
-
end
-
end
-
-
# Represents a single interaction over HTTP, containing a request and a response.
-
#
-
# @attr [Request] request the request
-
# @attr [Response] response the response
-
# @attr [Time] recorded_at when this HTTP interaction was recorded
-
1
class HTTPInteraction < Struct.new(:request, :response, :recorded_at)
-
1
def initialize(*args)
-
4
super
-
4
self.recorded_at ||= Time.now
-
end
-
-
# Builds a serializable hash from the HTTP interaction data.
-
#
-
# @return [Hash] hash that represents this HTTP interaction
-
# and can be easily serialized.
-
# @see HTTPInteraction.from_hash
-
1
def to_hash
-
{
-
'request' => request.to_hash,
-
'response' => response.to_hash,
-
'recorded_at' => recorded_at.httpdate
-
}.tap do |hash|
-
OrderedHashSerializer.apply_to(hash, members)
-
end
-
end
-
-
# Constructs a new instance from a hash.
-
#
-
# @param [Hash] hash the hash to use to construct the instance.
-
# @return [HTTPInteraction] the HTTP interaction
-
1
def self.from_hash(hash)
-
4
new Request.from_hash(hash.fetch('request', {})),
-
Response.from_hash(hash.fetch('response', {})),
-
Time.httpdate(hash.fetch('recorded_at'))
-
end
-
-
# @return [HookAware] an instance with additional capabilities
-
# suitable for use in `before_record` and `before_playback` hooks.
-
1
def hook_aware
-
4
HookAware.new(self)
-
end
-
-
# Decorates an {HTTPInteraction} with additional methods useful
-
# for a `before_record` or `before_playback` hook.
-
1
class HookAware < DelegateClass(HTTPInteraction)
-
1
def initialize(http_interaction)
-
4
@ignored = false
-
4
super
-
end
-
-
# Flags the HTTP interaction so that VCR ignores it. This is useful in
-
# a {VCR::Configuration#before_record} or {VCR::Configuration#before_playback}
-
# hook so that VCR does not record or play it back.
-
# @see #ignored?
-
1
def ignore!
-
@ignored = true
-
end
-
-
# @return [Boolean] whether or not this HTTP interaction should be ignored.
-
# @see #ignore!
-
1
def ignored?
-
4
!!@ignored
-
end
-
-
# Replaces a string in any part of the HTTP interaction (headers, request body,
-
# response body, etc) with the given replacement text.
-
#
-
# @param [#to_s] text the text to replace
-
# @param [#to_s] replacement_text the text to put in its place
-
1
def filter!(text, replacement_text)
-
text, replacement_text = text.to_s, replacement_text.to_s
-
return self if [text, replacement_text].any? { |t| t.empty? }
-
filter_object!(self, text, replacement_text)
-
end
-
-
1
private
-
-
1
def filter_object!(object, text, replacement_text)
-
if object.respond_to?(:gsub)
-
object.gsub!(text, replacement_text) if object.include?(text)
-
elsif Hash === object
-
filter_hash!(object, text, replacement_text)
-
elsif object.respond_to?(:each)
-
# This handles nested arrays and structs
-
object.each { |o| filter_object!(o, text, replacement_text) }
-
end
-
-
object
-
end
-
-
1
def filter_hash!(hash, text, replacement_text)
-
filter_object!(hash.values, text, replacement_text)
-
-
hash.keys.each do |k|
-
new_key = filter_object!(k.dup, text, replacement_text)
-
hash[new_key] = hash.delete(k) unless k == new_key
-
end
-
end
-
end
-
end
-
end
-
1
require 'vcr/util/variable_args_block_caller'
-
-
1
module VCR
-
# @private
-
1
module Hooks
-
1
include VariableArgsBlockCaller
-
-
1
FilteredHook = Struct.new(:hook, :filters) do
-
1
include VariableArgsBlockCaller
-
-
1
def conditionally_invoke(*args)
-
12
filters = Array(self.filters)
-
16
return if filters.any? { |f| !call_block(f.to_proc, *args) }
-
8
call_block(hook, *args)
-
end
-
end
-
-
1
def self.included(klass)
-
2
klass.class_eval do
-
2
extend ClassMethods
-
2
hooks_module = Module.new
-
2
const_set("DefinedHooks", hooks_module)
-
2
include hooks_module
-
end
-
end
-
-
1
def invoke_hook(hook_type, *args)
-
17
hooks[hook_type].map do |hook|
-
12
hook.conditionally_invoke(*args)
-
end
-
end
-
-
1
def clear_hooks
-
hooks.clear
-
end
-
-
1
def hooks
-
@hooks ||= Hash.new do |hash, hook_type|
-
7
hash[hook_type] = []
-
26
end
-
end
-
-
1
def has_hooks_for?(hook_type)
-
4
hooks[hook_type].any?
-
end
-
-
# @private
-
1
module ClassMethods
-
1
def define_hook(hook_type, prepend = false)
-
7
placement_method = prepend ? :unshift : :<<
-
-
# Put the hook methods in a module so we can override and super to these methods.
-
7
self::DefinedHooks.module_eval do
-
7
define_method hook_type do |*filters, &hook|
-
5
hooks[hook_type].send(placement_method, FilteredHook.new(hook, filters))
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module VCR
-
# @private
-
# Provides log message formatting helper methods.
-
1
class Logger
-
1
def initialize(stream)
-
@stream = stream
-
end
-
-
1
def log(message, log_prefix, indentation_level = 0)
-
indentation = ' ' * indentation_level
-
log_message = indentation + log_prefix + message
-
@stream.puts log_message
-
end
-
-
1
def request_summary(request, request_matchers)
-
attributes = [request.method, request.uri]
-
attributes << request.body.to_s[0, 80].inspect if request_matchers.include?(:body)
-
attributes << request.headers.inspect if request_matchers.include?(:headers)
-
"[#{attributes.join(" ")}]"
-
end
-
-
1
def response_summary(response)
-
"[#{response.status.code} #{response.body[0, 80].inspect}]"
-
end
-
-
# @private
-
# A null-object version of the Logger. Used when
-
# a `debug_logger` has not been set.
-
#
-
# @note We used to use a null object for the `debug_logger` itself,
-
# but some users noticed a negative perf impact from having the
-
# logger formatting logic still executing in that case, so we
-
# moved the null object interface up a layer to here.
-
1
module Null
-
1
module_function
-
-
1
def log(*); end
-
1
def request_summary(*); end
-
1
def response_summary(*); end
-
end
-
-
# @private
-
# Provides common logger helper methods that simply delegate to
-
# the underlying logger object.
-
1
module Mixin
-
1
def log(message, indentation_level = 0)
-
32
VCR.configuration.logger.log(message, log_prefix, indentation_level)
-
end
-
-
1
def request_summary(*args)
-
40
VCR.configuration.logger.request_summary(*args)
-
end
-
-
1
def response_summary(*args)
-
8
VCR.configuration.logger.response_summary(*args)
-
end
-
end
-
end
-
end
-
1
module VCR
-
# @private
-
1
module VariableArgsBlockCaller
-
1
def call_block(block, *args)
-
16
if block.arity >= 0
-
16
args = args.first([args.size, block.arity].min)
-
end
-
-
16
block.call(*args)
-
end
-
end
-
end
-
-
1
module VCR
-
# @private
-
1
class VersionChecker
-
1
def initialize(library_name, library_version, min_version)
-
1
@library_name = library_name
-
1
@library_version = library_version
-
1
@min_version = min_version
-
-
1
@major, @minor, @patch = parse_version(library_version)
-
1
@min_major, @min_minor, @min_patch = parse_version(min_version)
-
end
-
-
1
def check_version!
-
1
raise_too_low_error if too_low?
-
end
-
-
1
private
-
-
1
def too_low?
-
1
compare_version == :too_low
-
end
-
-
1
def raise_too_low_error
-
raise Errors::LibraryVersionTooLowError,
-
"You are using #{@library_name} #{@library_version}. " +
-
"VCR requires version #{version_requirement}."
-
end
-
-
1
def compare_version
-
case
-
when @major < @min_major then :too_low
-
when @major > @min_major then :ok
-
when @minor < @min_minor then :too_low
-
1
when @minor > @min_minor then :ok
-
when @patch < @min_patch then :too_low
-
1
end
-
end
-
-
1
def version_requirement
-
">= #{@min_version}"
-
end
-
-
1
def parse_version(version)
-
8
version.split('.').map { |v| v.to_i }
-
end
-
end
-
end
-
-
1
module VCR
-
1
extend self
-
-
# @return [String] the current VCR version.
-
# @note This string also has singleton methods:
-
#
-
# * `major` [Integer] The major version.
-
# * `minor` [Integer] The minor version.
-
# * `patch` [Integer] The patch version.
-
# * `parts` [Array<Integer>] List of the version parts.
-
1
def version
-
@version ||= begin
-
string = '3.0.1'
-
-
def string.parts
-
split('.').map { |p| p.to_i }
-
end
-
-
def string.major
-
parts[0]
-
end
-
-
def string.minor
-
parts[1]
-
end
-
-
def string.patch
-
parts[2]
-
end
-
-
string
-
end
-
end
-
end
-
1
require 'singleton'
-
-
1
require 'addressable/uri'
-
1
require 'addressable/template'
-
1
require 'crack/xml'
-
-
1
require 'webmock/deprecation'
-
1
require 'webmock/version'
-
-
1
require 'webmock/errors'
-
-
1
require 'webmock/util/query_mapper'
-
1
require 'webmock/util/uri'
-
1
require 'webmock/util/headers'
-
1
require 'webmock/util/hash_counter'
-
1
require 'webmock/util/hash_keys_stringifier'
-
1
require 'webmock/util/json'
-
1
require 'webmock/util/version_checker'
-
1
require 'webmock/util/hash_validator'
-
-
1
require 'webmock/matchers/hash_including_matcher'
-
-
1
require 'webmock/request_pattern'
-
1
require 'webmock/request_signature'
-
1
require 'webmock/responses_sequence'
-
1
require 'webmock/request_stub'
-
1
require 'webmock/response'
-
1
require 'webmock/rack_response'
-
-
1
require 'webmock/stub_request_snippet'
-
1
require 'webmock/request_signature_snippet'
-
1
require 'webmock/request_body_diff'
-
-
1
require 'webmock/assertion_failure'
-
1
require 'webmock/request_execution_verifier'
-
1
require 'webmock/config'
-
1
require 'webmock/callback_registry'
-
1
require 'webmock/request_registry'
-
1
require 'webmock/stub_registry'
-
1
require 'webmock/api'
-
-
1
require 'webmock/http_lib_adapters/http_lib_adapter_registry'
-
1
require 'webmock/http_lib_adapters/http_lib_adapter'
-
1
require 'webmock/http_lib_adapters/net_http'
-
1
require 'webmock/http_lib_adapters/http_rb_adapter'
-
1
require 'webmock/http_lib_adapters/httpclient_adapter'
-
1
require 'webmock/http_lib_adapters/patron_adapter'
-
1
require 'webmock/http_lib_adapters/curb_adapter'
-
1
require 'webmock/http_lib_adapters/em_http_request_adapter'
-
1
require 'webmock/http_lib_adapters/typhoeus_hydra_adapter'
-
1
require 'webmock/http_lib_adapters/excon_adapter'
-
1
require 'webmock/http_lib_adapters/manticore_adapter'
-
-
1
require 'webmock/webmock'
-
-
1
if RUBY_VERSION <= "1.8.7" && Addressable::VERSION::STRING >= "2.4.0"
-
raise StandardError,
-
<<-ERR
-
\n\e[31m
-
Addressable dropped support for Ruby 1.8.7 on version 2.4.0,
-
-
please add the following to your Gemfile to be able to use WebMock:
-
-
gem 'addressable', '< 2.4.0'\e[0m\n
-
ERR
-
end
-
1
module WebMock
-
1
module API
-
1
extend self
-
-
1
def stub_request(method, uri)
-
WebMock::StubRegistry.instance.
-
register_request_stub(WebMock::RequestStub.new(method, uri))
-
end
-
-
1
alias_method :stub_http_request, :stub_request
-
-
1
def a_request(method, uri)
-
WebMock::RequestPattern.new(method, uri)
-
end
-
-
1
class << self
-
1
alias :request :a_request
-
end
-
-
1
def assert_requested(*args, &block)
-
if not args[0].is_a?(WebMock::RequestStub)
-
args = convert_uri_method_and_options_to_request_and_options(args[0], args[1], args[2], &block)
-
elsif block
-
raise ArgumentError, "assert_requested with a stub object, doesn't accept blocks"
-
end
-
assert_request_requested(*args)
-
end
-
-
1
def assert_not_requested(*args, &block)
-
if not args[0].is_a?(WebMock::RequestStub)
-
args = convert_uri_method_and_options_to_request_and_options(args[0], args[1], args[2], &block)
-
elsif block
-
raise ArgumentError, "assert_not_requested with a stub object, doesn't accept blocks"
-
end
-
assert_request_not_requested(*args)
-
end
-
-
# Similar to RSpec::Mocks::ArgumentMatchers#hash_including()
-
#
-
# Matches a hash that includes the specified key(s) or key/value pairs.
-
# Ignores any additional keys.
-
#
-
# @example
-
#
-
# object.should_receive(:message).with(hash_including(:key => val))
-
# object.should_receive(:message).with(hash_including(:key))
-
# object.should_receive(:message).with(hash_including(:key, :key2 => val2))
-
1
def hash_including(*args)
-
if defined?(super)
-
super
-
else
-
WebMock::Matchers::HashIncludingMatcher.new(anythingize_lonely_keys(*args))
-
end
-
end
-
-
1
def remove_request_stub(stub)
-
WebMock::StubRegistry.instance.remove_request_stub(stub)
-
end
-
-
1
private
-
-
1
def convert_uri_method_and_options_to_request_and_options(method, uri, options, &block)
-
options ||= {}
-
options_for_pattern = options.dup
-
[:times, :at_least_times, :at_most_times].each { |key| options_for_pattern.delete(key) }
-
request = WebMock::RequestPattern.new(method, uri, options_for_pattern)
-
request = request.with(&block) if block
-
[request, options]
-
end
-
-
1
def assert_request_requested(request, options = {})
-
times = options.delete(:times)
-
at_least_times = options.delete(:at_least_times)
-
at_most_times = options.delete(:at_most_times)
-
times = 1 if times.nil? && at_least_times.nil? && at_most_times.nil?
-
verifier = WebMock::RequestExecutionVerifier.new(request, times, at_least_times, at_most_times)
-
WebMock::AssertionFailure.failure(verifier.failure_message) unless verifier.matches?
-
end
-
-
1
def assert_request_not_requested(request, options = {})
-
times = options.delete(:times)
-
at_least_times = options.delete(:at_least_times)
-
at_most_times = options.delete(:at_most_times)
-
verifier = WebMock::RequestExecutionVerifier.new(request, times, at_least_times, at_most_times)
-
WebMock::AssertionFailure.failure(verifier.failure_message_when_negated) unless verifier.does_not_match?
-
end
-
-
#this is a based on RSpec::Mocks::ArgumentMatchers#anythingize_lonely_keys
-
1
def anythingize_lonely_keys(*args)
-
hash = args.last.class == Hash ? args.delete_at(-1) : {}
-
args.each { | arg | hash[arg] = WebMock::Matchers::AnyArgMatcher.new(nil) }
-
hash
-
end
-
-
end
-
end
-
1
module WebMock
-
1
class AssertionFailure
-
1
@error_class = RuntimeError
-
1
class << self
-
1
attr_accessor :error_class
-
1
def failure(message)
-
raise @error_class.new(message)
-
end
-
end
-
end
-
end
-
1
module WebMock
-
1
class CallbackRegistry
-
1
@@callbacks = []
-
-
1
def self.add_callback(options, block)
-
2
@@callbacks << {:options => options, :block => block}
-
end
-
-
1
def self.callbacks
-
4
@@callbacks
-
end
-
-
1
def self.invoke_callbacks(options, request_signature, response)
-
4
return if @@callbacks.empty?
-
4
CallbackRegistry.callbacks.each do |callback|
-
8
except = callback[:options][:except]
-
8
real_only = callback[:options][:real_requests_only]
-
8
unless except && except.include?(options[:lib])
-
8
if !real_only || options[:real_request]
-
4
callback[:block].call(request_signature, response)
-
end
-
end
-
end
-
end
-
-
1
def self.reset
-
@@callbacks = []
-
end
-
-
1
def self.any_callbacks?
-
!@@callbacks.empty?
-
end
-
-
end
-
end
-
1
module WebMock
-
1
class Config
-
1
include Singleton
-
-
1
def initialize
-
1
@show_stubbing_instructions = true
-
1
@show_body_diff = true
-
end
-
-
1
attr_accessor :allow_net_connect
-
1
attr_accessor :allow_localhost
-
1
attr_accessor :allow
-
1
attr_accessor :net_http_connect_on_start
-
1
attr_accessor :show_stubbing_instructions
-
1
attr_accessor :query_values_notation
-
1
attr_accessor :show_body_diff
-
end
-
end
-
1
module WebMock
-
1
class Deprecation
-
1
class << self
-
1
def warning(message)
-
warn "WebMock deprecation warning: #{message}"
-
end
-
end
-
end
-
end
-
1
module WebMock
-
-
1
class NetConnectNotAllowedError < Exception
-
1
def initialize(request_signature)
-
request_signature_snippet = RequestSignatureSnippet.new(request_signature)
-
text = [
-
"Real HTTP connections are disabled. Unregistered request: #{request_signature}",
-
request_signature_snippet.stubbing_instructions,
-
request_signature_snippet.request_stubs,
-
"="*60
-
].compact.join("\n\n")
-
super(text)
-
end
-
-
end
-
-
end
-
1
begin
-
1
require 'curb'
-
rescue LoadError
-
# curb not found
-
end
-
-
1
if defined?(Curl)
-
WebMock::VersionChecker.new('Curb', Curl::CURB_VERSION, '0.7.16', '0.9.1', ['0.8.7']).check_version!
-
-
module WebMock
-
module HttpLibAdapters
-
class CurbAdapter < HttpLibAdapter
-
adapter_for :curb
-
-
OriginalCurlEasy = Curl::Easy unless const_defined?(:OriginalCurlEasy)
-
-
def self.enable!
-
Curl.send(:remove_const, :Easy)
-
Curl.send(:const_set, :Easy, Curl::WebMockCurlEasy)
-
end
-
-
def self.disable!
-
Curl.send(:remove_const, :Easy)
-
Curl.send(:const_set, :Easy, OriginalCurlEasy)
-
end
-
-
# Borrowed from Patron:
-
# http://github.com/toland/patron/blob/master/lib/patron/response.rb
-
def self.parse_header_string(header_string)
-
status, headers = nil, {}
-
-
header_string.split(/\r\n/).each do |header|
-
if header =~ %r|^HTTP/1.[01] \d\d\d (.*)|
-
status = $1
-
else
-
parts = header.split(':', 2)
-
unless parts.empty?
-
parts[1].strip! unless parts[1].nil?
-
if headers.has_key?(parts[0])
-
headers[parts[0]] = [headers[parts[0]]] unless headers[parts[0]].kind_of? Array
-
headers[parts[0]] << parts[1]
-
else
-
headers[parts[0]] = parts[1]
-
end
-
end
-
end
-
end
-
-
return status, headers
-
end
-
end
-
end
-
end
-
-
module Curl
-
class WebMockCurlEasy < Curl::Easy
-
-
def curb_or_webmock
-
request_signature = build_request_signature
-
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
-
-
if webmock_response = WebMock::StubRegistry.instance.response_for_request(request_signature)
-
build_curb_response(webmock_response)
-
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :curb}, request_signature, webmock_response)
-
invoke_curb_callbacks
-
true
-
elsif WebMock.net_connect_allowed?(request_signature.uri)
-
res = yield
-
if WebMock::CallbackRegistry.any_callbacks?
-
webmock_response = build_webmock_response
-
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :curb, :real_request => true}, request_signature,
-
webmock_response)
-
end
-
res
-
else
-
raise WebMock::NetConnectNotAllowedError.new(request_signature)
-
end
-
end
-
-
def build_request_signature
-
method = @webmock_method.to_s.downcase.to_sym
-
-
uri = WebMock::Util::URI.heuristic_parse(self.url)
-
uri.path = uri.normalized_path.gsub("[^:]//","/")
-
uri.user = self.username
-
uri.password = self.password
-
-
request_body = case method
-
when :post
-
self.post_body || @post_body
-
when :put
-
@put_data
-
else
-
nil
-
end
-
-
request_signature = WebMock::RequestSignature.new(
-
method,
-
uri.to_s,
-
:body => request_body,
-
:headers => self.headers
-
)
-
request_signature
-
end
-
-
def build_curb_response(webmock_response)
-
raise Curl::Err::TimeoutError if webmock_response.should_timeout
-
webmock_response.raise_error_if_any
-
-
@body_str = webmock_response.body
-
@response_code = webmock_response.status[0]
-
-
@header_str = "HTTP/1.1 #{webmock_response.status[0]} #{webmock_response.status[1]}\r\n"
-
if webmock_response.headers
-
@header_str << webmock_response.headers.map do |k,v|
-
"#{k}: #{v.is_a?(Array) ? v.join(", ") : v}"
-
end.join("\r\n")
-
-
location = webmock_response.headers['Location']
-
if self.follow_location? && location
-
@last_effective_url = location
-
webmock_follow_location(location)
-
end
-
-
@content_type = webmock_response.headers["Content-Type"]
-
@transfer_encoding = webmock_response.headers["Transfer-Encoding"]
-
end
-
-
@last_effective_url ||= self.url
-
end
-
-
def webmock_follow_location(location)
-
first_url = self.url
-
self.url = location
-
-
curb_or_webmock do
-
send( "http_#{@webmock_method}_without_webmock" )
-
end
-
-
self.url = first_url
-
end
-
-
def invoke_curb_callbacks
-
@on_progress.call(0.0,1.0,0.0,1.0) if defined?( @on_progress )
-
self.header_str.lines.each { |header_line| @on_header.call header_line } if defined?( @on_header )
-
if defined?( @on_body )
-
if chunked_response?
-
self.body_str.each do |chunk|
-
@on_body.call(chunk)
-
end
-
else
-
@on_body.call(self.body_str)
-
end
-
end
-
@on_complete.call(self) if defined?( @on_complete )
-
-
case response_code
-
when 200..299
-
@on_success.call(self) if defined?( @on_success )
-
when 400..499
-
@on_missing.call(self, self.response_code) if defined?( @on_missing )
-
when 500..599
-
@on_failure.call(self, self.response_code) if defined?( @on_failure )
-
end
-
end
-
-
def chunked_response?
-
defined?( @transfer_encoding ) && @transfer_encoding == 'chunked' && self.body_str.respond_to?(:each)
-
end
-
-
def build_webmock_response
-
status, headers =
-
WebMock::HttpLibAdapters::CurbAdapter.parse_header_string(self.header_str)
-
-
webmock_response = WebMock::Response.new
-
webmock_response.status = [self.response_code, status]
-
webmock_response.body = self.body_str
-
webmock_response.headers = headers
-
webmock_response
-
end
-
-
###
-
### Mocks of Curl::Easy methods below here.
-
###
-
-
def http(method)
-
@webmock_method = method
-
super
-
end
-
-
%w[ get head delete ].each do |verb|
-
define_method "http_#{verb}" do
-
@webmock_method = verb
-
super()
-
end
-
end
-
-
def http_put data = nil
-
@webmock_method = :put
-
@put_data = data if data
-
super
-
end
-
alias put http_put
-
-
def http_post *data
-
@webmock_method = :post
-
@post_body = data.join('&') if data && !data.empty?
-
super
-
end
-
alias post http_post
-
-
def perform
-
@webmock_method ||= :get
-
curb_or_webmock { super }
-
end
-
-
def put_data= data
-
@webmock_method = :put
-
@put_data = data
-
super
-
end
-
-
def post_body= data
-
@webmock_method = :post
-
super
-
end
-
-
def delete= value
-
@webmock_method = :delete if value
-
super
-
end
-
-
def head= value
-
@webmock_method = :head if value
-
super
-
end
-
-
def body_str
-
@body_str ||= super
-
end
-
alias body body_str
-
-
def response_code
-
@response_code ||= super
-
end
-
-
def header_str
-
@header_str ||= super
-
end
-
alias head header_str
-
-
def last_effective_url
-
@last_effective_url ||= super
-
end
-
-
def content_type
-
@content_type ||= super
-
end
-
-
%w[ success failure missing header body complete progress ].each do |callback|
-
class_eval <<-METHOD, __FILE__, __LINE__
-
def on_#{callback} &block
-
@on_#{callback} = block
-
super
-
end
-
METHOD
-
end
-
end
-
end
-
end
-
1
if defined?(EventMachine::HttpRequest)
-
module WebMock
-
module HttpLibAdapters
-
class EmHttpRequestAdapter < HttpLibAdapter
-
adapter_for :em_http_request
-
-
OriginalHttpRequest = EventMachine::HttpRequest unless const_defined?(:OriginalHttpRequest)
-
-
def self.enable!
-
EventMachine.send(:remove_const, :HttpRequest)
-
EventMachine.send(:const_set, :HttpRequest, EventMachine::WebMockHttpRequest)
-
end
-
-
def self.disable!
-
EventMachine.send(:remove_const, :HttpRequest)
-
EventMachine.send(:const_set, :HttpRequest, OriginalHttpRequest)
-
end
-
end
-
end
-
end
-
-
-
module EventMachine
-
class WebMockHttpRequest < EventMachine::HttpRequest
-
-
include HttpEncoding
-
-
class WebMockHttpClient < EventMachine::HttpClient
-
-
def setup(response, uri, error = nil)
-
@last_effective_url = @uri = uri
-
if error
-
on_error(error)
-
fail(self)
-
else
-
EM.next_tick do
-
receive_data(response)
-
succeed(self)
-
end
-
end
-
end
-
-
def unbind
-
end
-
-
def close_connection
-
end
-
end
-
-
def send_request(&block)
-
request_signature = build_request_signature
-
-
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
-
-
if webmock_response = WebMock::StubRegistry.instance.response_for_request(request_signature)
-
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :em_http_request}, request_signature, webmock_response)
-
client = WebMockHttpClient.new(nil)
-
client.on_error("WebMock timeout error") if webmock_response.should_timeout
-
client.setup(make_raw_response(webmock_response), @uri,
-
webmock_response.should_timeout ? "WebMock timeout error" : nil)
-
client
-
elsif WebMock.net_connect_allowed?(request_signature.uri)
-
http = super
-
http.callback {
-
if WebMock::CallbackRegistry.any_callbacks?
-
webmock_response = build_webmock_response(http)
-
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :em_http_request, :real_request => true}, request_signature,
-
webmock_response)
-
end
-
}
-
http
-
else
-
raise WebMock::NetConnectNotAllowedError.new(request_signature)
-
end
-
end
-
-
private
-
-
def build_webmock_response(http)
-
webmock_response = WebMock::Response.new
-
webmock_response.status = [http.response_header.status, http.response_header.http_reason]
-
webmock_response.headers = http.response_header
-
webmock_response.body = http.response
-
webmock_response
-
end
-
-
def build_request_signature
-
if @req
-
options = @req.options
-
method = @req.method
-
uri = @req.uri.dup
-
else
-
options = @options
-
method = @method
-
uri = @uri.dup
-
end
-
-
if options[:authorization] || options['authorization']
-
auth = (options[:authorization] || options['authorization'])
-
userinfo = auth.join(':')
-
userinfo = WebMock::Util::URI.encode_unsafe_chars_in_userinfo(userinfo)
-
options.reject! {|k,v| k.to_s == 'authorization' } #we added it to url userinfo
-
uri.userinfo = userinfo
-
end
-
-
uri.query = encode_query(@req.uri, options[:query]).slice(/\?(.*)/, 1)
-
-
body = options[:body] || options['body']
-
body = form_encode_body(body) if body.is_a?(Hash)
-
-
WebMock::RequestSignature.new(
-
method.downcase.to_sym,
-
uri.to_s,
-
:body => body,
-
:headers => (options[:head] || options['head'])
-
)
-
end
-
-
-
def make_raw_response(response)
-
response.raise_error_if_any
-
-
status, headers, body = response.status, response.headers, response.body
-
-
response_string = []
-
response_string << "HTTP/1.1 #{status[0]} #{status[1]}"
-
-
headers.each do |header, value|
-
value = value.join(", ") if value.is_a?(Array)
-
-
# WebMock's internal processing will not handle the body
-
# correctly if the header indicates that it is chunked, unless
-
# we also create all the chunks.
-
# It's far easier just to remove the header.
-
next if header =~ /transfer-encoding/i && value =~/chunked/i
-
-
response_string << "#{header}: #{value}"
-
end if headers
-
-
response_string << "" << body
-
response_string.join("\n")
-
end
-
end
-
end
-
end
-
1
begin
-
1
require 'em-http-request'
-
rescue LoadError
-
# em-http-request not found
-
end
-
-
1
if defined?(EventMachine::HttpConnection)
-
require File.expand_path(File.dirname(__FILE__) + '/em_http_request/em_http_request_1_x')
-
else
-
1
require File.expand_path(File.dirname(__FILE__) + '/em_http_request/em_http_request_0_x')
-
end
-
1
begin
-
1
require 'excon'
-
rescue LoadError
-
# excon not found
-
end
-
-
1
if defined?(Excon)
-
WebMock::VersionChecker.new('Excon', Excon::VERSION, '0.27.5').check_version!
-
-
module WebMock
-
module HttpLibAdapters
-
-
class ExconAdapter < HttpLibAdapter
-
PARAMS_TO_DELETE = [:expects, :idempotent,
-
:instrumentor_name, :instrumentor,
-
:response_block,
-
:__construction_args, :stack,
-
:connection, :response]
-
-
adapter_for :excon
-
-
def self.enable!
-
self.add_excon_stub
-
end
-
-
def self.disable!
-
self.remove_excon_stub
-
end
-
-
def self.add_excon_stub
-
if not @stub
-
@original_excon_mock_default = ::Excon.defaults[:mock]
-
::Excon.defaults[:mock] = true
-
@stub = ::Excon.stub({}) do |params|
-
self.handle_request(params)
-
end
-
end
-
end
-
-
def self.remove_excon_stub
-
::Excon.defaults[:mock] = @original_excon_mock_default
-
@original_excon_mock_default = nil
-
Excon.stubs.delete(@stub)
-
@stub = nil
-
end
-
-
def self.handle_request(params)
-
mock_request = self.build_request params.dup
-
WebMock::RequestRegistry.instance.requested_signatures.put(mock_request)
-
-
if mock_response = WebMock::StubRegistry.instance.response_for_request(mock_request)
-
self.perform_callbacks(mock_request, mock_response, :real_request => false)
-
response = self.real_response(mock_response)
-
response
-
elsif WebMock.net_connect_allowed?(mock_request.uri)
-
conn = new_excon_connection(params)
-
real_response = conn.request(request_params_from(params.merge(:mock => false)))
-
-
ExconAdapter.perform_callbacks(mock_request, ExconAdapter.mock_response(real_response), :real_request => true)
-
-
real_response.data
-
else
-
raise WebMock::NetConnectNotAllowedError.new(mock_request)
-
end
-
end
-
-
def self.new_excon_connection(params)
-
# Ensure the connection is constructed with the exact same args
-
# that the orginal connection was constructed with.
-
args = params.fetch(:__construction_args)
-
::Excon::Connection.new(connection_params_from args.merge(:mock => false))
-
end
-
-
def self.connection_params_from(hash)
-
hash = hash.dup
-
PARAMS_TO_DELETE.each { |key| hash.delete(key) }
-
hash
-
end
-
-
def self.request_params_from(hash)
-
hash = hash.dup
-
if defined?(Excon::VALID_REQUEST_KEYS)
-
hash.reject! {|key,_| !Excon::VALID_REQUEST_KEYS.include?(key) }
-
end
-
PARAMS_TO_DELETE.each { |key| hash.delete(key) }
-
hash
-
end
-
-
def self.to_query(hash)
-
string = ""
-
for key, values in hash
-
if values.nil?
-
string << key.to_s << '&'
-
else
-
for value in [*values]
-
string << key.to_s << '=' << CGI.escape(value.to_s) << '&'
-
end
-
end
-
end
-
string.chop! # remove trailing '&'
-
end
-
-
def self.build_request(params)
-
params = params.dup
-
method = (params.delete(:method) || :get).to_s.downcase.to_sym
-
params[:query] = to_query(params[:query]) if params[:query].is_a?(Hash)
-
uri = Addressable::URI.new(params).to_s
-
WebMock::RequestSignature.new method, uri, :body => body_from(params), :headers => params[:headers]
-
end
-
-
def self.body_from(params)
-
body = params[:body]
-
return body unless body.respond_to?(:read)
-
-
contents = body.read
-
body.rewind if body.respond_to?(:rewind)
-
contents
-
end
-
-
def self.real_response(mock)
-
raise Excon::Errors::Timeout if mock.should_timeout
-
mock.raise_error_if_any
-
{
-
:body => mock.body,
-
:status => mock.status[0].to_i,
-
:headers => mock.headers || {}
-
}
-
end
-
-
def self.mock_response(real)
-
mock = WebMock::Response.new
-
mock.status = real.status
-
mock.headers = real.headers
-
mock.body = real.body.dup
-
mock
-
end
-
-
def self.perform_callbacks(request, response, options = {})
-
return unless WebMock::CallbackRegistry.any_callbacks?
-
WebMock::CallbackRegistry.invoke_callbacks(options.merge(:lib => :excon), request, response)
-
end
-
end
-
end
-
end
-
-
Excon::Connection.class_eval do
-
def self.new(args)
-
args.delete(:__construction_args)
-
super(args).tap do |instance|
-
instance.data[:__construction_args] = args
-
end
-
end
-
end
-
end
-
1
module WebMock
-
1
class HttpLibAdapter
-
1
def self.adapter_for(lib)
-
1
WebMock::HttpLibAdapterRegistry.instance.register(lib, self)
-
end
-
end
-
end
-
1
module WebMock
-
1
class HttpLibAdapterRegistry
-
1
include Singleton
-
-
1
attr_accessor :http_lib_adapters
-
-
1
def initialize
-
1
@http_lib_adapters = {}
-
end
-
-
1
def register(lib, adapter)
-
1
@http_lib_adapters[lib] = adapter
-
end
-
-
1
def each_adapter(&block)
-
1
@http_lib_adapters.each(&block)
-
end
-
end
-
end
-
1
begin
-
1
require "http"
-
rescue LoadError
-
# HTTP gem not found
-
end
-
-
1
if defined?(HTTP) && defined?(HTTP::VERSION)
-
WebMock::VersionChecker.new("HTTP Gem", HTTP::VERSION, "0.6.0").check_version!
-
-
module WebMock
-
module HttpLibAdapters
-
class HttpRbAdapter < HttpLibAdapter
-
adapter_for :http_rb
-
-
class << self
-
def enable!
-
@enabled = true
-
end
-
-
def disable!
-
@enabled = false
-
end
-
-
def enabled?
-
@enabled
-
end
-
end
-
end
-
end
-
end
-
-
require "webmock/http_lib_adapters/http_rb/client"
-
require "webmock/http_lib_adapters/http_rb/request"
-
require "webmock/http_lib_adapters/http_rb/response"
-
require "webmock/http_lib_adapters/http_rb/streamer"
-
require "webmock/http_lib_adapters/http_rb/webmock"
-
end
-
1
begin
-
1
require 'httpclient'
-
require 'jsonclient' # defined in 'httpclient' gem as well
-
rescue LoadError
-
# httpclient not found
-
# or jsonclient not defined (in old versions of httclient gem)
-
end
-
-
1
if defined?(::HTTPClient)
-
-
module WebMock
-
module HttpLibAdapters
-
class HTTPClientAdapter < HttpLibAdapter
-
adapter_for :httpclient
-
-
unless const_defined?(:OriginalHttpClient)
-
OriginalHttpClient = ::HTTPClient
-
end
-
-
unless const_defined?(:OriginalJsonClient)
-
OriginalJsonClient = ::JSONClient if defined?(::JSONClient)
-
end
-
-
def self.enable!
-
Object.send(:remove_const, :HTTPClient)
-
Object.send(:const_set, :HTTPClient, WebMockHTTPClient)
-
if defined? ::JSONClient
-
Object.send(:remove_const, :JSONClient)
-
Object.send(:const_set, :JSONClient, WebMockJSONClient)
-
end
-
end
-
-
def self.disable!
-
Object.send(:remove_const, :HTTPClient)
-
Object.send(:const_set, :HTTPClient, OriginalHttpClient)
-
if defined? ::JSONClient
-
Object.send(:remove_const, :JSONClient)
-
Object.send(:const_set, :JSONClient, OriginalJsonClient)
-
end
-
end
-
end
-
end
-
end
-
-
module WebMockHTTPClients
-
def do_get_block(req, proxy, conn, &block)
-
do_get(req, proxy, conn, false, &block)
-
end
-
-
def do_get_stream(req, proxy, conn, &block)
-
do_get(req, proxy, conn, true, &block)
-
end
-
-
def do_get(req, proxy, conn, stream = false, &block)
-
request_signature = build_request_signature(req, :reuse_existing)
-
-
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
-
-
if webmock_responses[request_signature]
-
webmock_response = webmock_responses.delete(request_signature)
-
response = build_httpclient_response(webmock_response, stream, req.header, &block)
-
@request_filter.each do |filter|
-
filter.filter_response(req, response)
-
end
-
res = conn.push(response)
-
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :httpclient}, request_signature, webmock_response)
-
res
-
elsif WebMock.net_connect_allowed?(request_signature.uri)
-
# in case there is a nil entry in the hash...
-
webmock_responses.delete(request_signature)
-
-
res = if stream
-
do_get_stream_without_webmock(req, proxy, conn, &block)
-
elsif block
-
body = ''
-
do_get_block_without_webmock(req, proxy, conn) do |http_res, chunk|
-
body += chunk
-
block.call(http_res, chunk)
-
end
-
else
-
do_get_block_without_webmock(req, proxy, conn)
-
end
-
res = conn.pop
-
conn.push(res)
-
if WebMock::CallbackRegistry.any_callbacks?
-
webmock_response = build_webmock_response(res, body)
-
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :httpclient, :real_request => true}, request_signature,
-
webmock_response)
-
end
-
res
-
else
-
raise WebMock::NetConnectNotAllowedError.new(request_signature)
-
end
-
end
-
-
def do_request_async(method, uri, query, body, extheader)
-
req = create_request(method, uri, query, body, extheader)
-
request_signature = build_request_signature(req)
-
webmock_request_signatures << request_signature
-
-
if webmock_responses[request_signature] || WebMock.net_connect_allowed?(request_signature.uri)
-
super
-
else
-
raise WebMock::NetConnectNotAllowedError.new(request_signature)
-
end
-
end
-
-
def build_httpclient_response(webmock_response, stream = false, req_header = nil, &block)
-
body = stream ? StringIO.new(webmock_response.body) : webmock_response.body
-
response = HTTP::Message.new_response(body, req_header)
-
response.header.init_response(webmock_response.status[0])
-
response.reason=webmock_response.status[1]
-
webmock_response.headers.to_a.each { |name, value| response.header.set(name, value) }
-
-
raise HTTPClient::TimeoutError if webmock_response.should_timeout
-
webmock_response.raise_error_if_any
-
-
block.call(response, body) if block
-
-
response
-
end
-
end
-
-
class WebMockHTTPClient < HTTPClient
-
alias_method :do_get_block_without_webmock, :do_get_block
-
alias_method :do_get_stream_without_webmock, :do_get_stream
-
-
include WebMockHTTPClients
-
end
-
-
if defined? ::JSONClient
-
class WebMockJSONClient < JSONClient
-
alias_method :do_get_block_without_webmock, :do_get_block
-
alias_method :do_get_stream_without_webmock, :do_get_stream
-
-
include WebMockHTTPClients
-
end
-
end
-
-
def build_webmock_response(httpclient_response, body = nil)
-
webmock_response = WebMock::Response.new
-
webmock_response.status = [httpclient_response.status, httpclient_response.reason]
-
-
webmock_response.headers = {}.tap do |hash|
-
httpclient_response.header.all.each do |(key, value)|
-
if hash.has_key?(key)
-
hash[key] = Array(hash[key]) + [value]
-
else
-
hash[key] = value
-
end
-
end
-
end
-
-
if body
-
webmock_response.body = body
-
elsif httpclient_response.content.respond_to?(:read)
-
webmock_response.body = httpclient_response.content.read
-
body = HTTP::Message::Body.new
-
body.init_response(StringIO.new(webmock_response.body))
-
httpclient_response.body = body
-
else
-
webmock_response.body = httpclient_response.content
-
end
-
webmock_response
-
end
-
-
def build_request_signature(req, reuse_existing = false)
-
uri = WebMock::Util::URI.heuristic_parse(req.header.request_uri.to_s)
-
uri.query = WebMock::Util::QueryMapper.values_to_query(req.header.request_query, :notation => WebMock::Config.instance.query_values_notation) if req.header.request_query
-
uri.port = req.header.request_uri.port
-
uri = uri.omit(:userinfo)
-
-
@request_filter.each do |filter|
-
filter.filter_request(req)
-
end
-
-
headers = req.header.all.inject({}) do |hdrs, header|
-
hdrs[header[0]] ||= []
-
hdrs[header[0]] << header[1]
-
hdrs
-
end
-
headers = headers_from_session(uri).merge(headers)
-
-
if auth_cred = auth_cred_from_www_auth(req) || auth_cred_from_headers(headers)
-
remove_authorization_header headers
-
uri.userinfo = userinfo_from_auth_cred auth_cred
-
end
-
-
signature = WebMock::RequestSignature.new(
-
req.header.request_method.downcase.to_sym,
-
uri.to_s,
-
:body => req.http_body.dump,
-
:headers => headers
-
)
-
-
# reuse a previous identical signature object if we stored one for later use
-
if reuse_existing && previous_signature = previous_signature_for(signature)
-
return previous_signature
-
end
-
-
signature
-
end
-
-
def userinfo_from_auth_cred auth_cred
-
userinfo = WebMock::Util::Headers.decode_userinfo_from_header(auth_cred)
-
WebMock::Util::URI.encode_unsafe_chars_in_userinfo(userinfo)
-
end
-
-
def remove_authorization_header headers
-
headers.reject! do |k, v|
-
next unless k =~ /[Aa]uthorization/
-
if v.is_a? Array
-
v.reject! { |v| v =~ /^Basic / }
-
v.length == 0
-
elsif v.is_a? String
-
v =~ /^Basic /
-
end
-
end
-
end
-
-
def auth_cred_from_www_auth(req)
-
auth = www_auth.basic_auth
-
auth.challenge(req.header.request_uri, nil)
-
auth.get(req) if auth.scheme == 'Basic'
-
end
-
-
def auth_cred_from_headers(headers)
-
headers.each do |k,v|
-
next unless k =~ /[Aa]uthorization/
-
return v if v.is_a?(String) && v =~ /^Basic /
-
v.each { |v| return v if v =~ /^Basic / } if v.is_a? Array
-
end
-
nil
-
end
-
-
def webmock_responses
-
@webmock_responses ||= Hash.new do |hash, request_signature|
-
hash[request_signature] = WebMock::StubRegistry.instance.response_for_request(request_signature)
-
end
-
end
-
-
def webmock_request_signatures
-
@webmock_request_signatures ||= []
-
end
-
-
def previous_signature_for(signature)
-
return nil unless index = webmock_request_signatures.index(signature)
-
webmock_request_signatures.delete_at(index)
-
end
-
-
private
-
-
# some of the headers sent by HTTPClient are derived from
-
# the client session
-
def headers_from_session(uri)
-
session_headers = HTTP::Message::Headers.new
-
@session_manager.send(:open, uri).send(:set_header, MessageMock.new(session_headers))
-
session_headers.all.inject({}) do |hdrs, header|
-
hdrs[header[0]] = header[1]
-
hdrs
-
end
-
end
-
-
# Mocks a HTTPClient HTTP::Message
-
class MessageMock
-
attr_reader :header
-
-
def initialize(headers)
-
@header = headers
-
end
-
-
def http_version=(value);end
-
end
-
-
end
-
1
begin
-
1
require 'manticore'
-
rescue LoadError
-
# manticore not found
-
end
-
-
1
if defined?(Manticore)
-
module WebMock
-
module HttpLibAdapters
-
class ManticoreAdapter < HttpLibAdapter
-
adapter_for :manticore
-
-
OriginalManticoreClient = Manticore::Client
-
-
def self.enable!
-
Manticore.send(:remove_const, :Client)
-
Manticore.send(:const_set, :Client, WebMockManticoreClient)
-
Manticore.instance_variable_set(:@manticore_facade, WebMockManticoreClient.new)
-
end
-
-
def self.disable!
-
Manticore.send(:remove_const, :Client)
-
Manticore.send(:const_set, :Client, OriginalManticoreClient)
-
Manticore.instance_variable_set(:@manticore_facade, OriginalManticoreClient.new)
-
end
-
-
class WebMockManticoreClient < Manticore::Client
-
def request(klass, url, options={}, &block)
-
super(klass, WebMock::Util::URI.normalize_uri(url).to_s, format_options(options))
-
end
-
-
private
-
-
def format_options(options)
-
return options unless headers = options[:headers]
-
-
options.merge(:headers => join_array_values(headers))
-
end
-
-
def join_array_values(headers)
-
headers.reduce({}) do |h, (k,v)|
-
v = v.join(', ') if v.is_a?(Array)
-
h.merge(k => v)
-
end
-
end
-
-
def response_object_for(request, context, &block)
-
request_signature = generate_webmock_request_signature(request)
-
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
-
-
if webmock_response = registered_response_for(request_signature)
-
webmock_response.raise_error_if_any
-
manticore_response = generate_manticore_response(webmock_response).call
-
real_request = false
-
-
elsif real_request_allowed?(request_signature.uri)
-
manticore_response = Manticore::Response.new(self, request, context, &block).call
-
webmock_response = generate_webmock_response(manticore_response)
-
real_request = true
-
-
else
-
raise WebMock::NetConnectNotAllowedError.new(request_signature)
-
end
-
-
WebMock::CallbackRegistry.invoke_callbacks({:lib => :manticore, :real_request => real_request}, request_signature, webmock_response)
-
manticore_response
-
end
-
-
def registered_response_for(request_signature)
-
WebMock::StubRegistry.instance.response_for_request(request_signature)
-
end
-
-
def real_request_allowed?(uri)
-
WebMock.net_connect_allowed?(uri)
-
end
-
-
def generate_webmock_request_signature(request)
-
method = request.method.downcase
-
uri = request.uri.to_s
-
body = read_body(request)
-
headers = split_array_values(request.headers)
-
-
WebMock::RequestSignature.new(method, uri, {:body => body, :headers => headers})
-
end
-
-
def read_body(request)
-
if request.respond_to?(:entity) && !request.entity.nil?
-
Manticore::EntityConverter.new.read_entity(request.entity)
-
end
-
end
-
-
def split_array_values(headers = [])
-
headers.each_with_object({}) do |(k, v), h|
-
h[k] = case v
-
when /,/ then v.split(',').map(&:strip)
-
else v
-
end
-
end
-
end
-
-
def generate_manticore_response(webmock_response)
-
raise Manticore::ConnectTimeout if webmock_response.should_timeout
-
-
Manticore::StubbedResponse.stub(
-
:code => webmock_response.status[0],
-
:body => webmock_response.body,
-
:headers => webmock_response.headers,
-
:cookies => {}
-
)
-
end
-
-
def generate_webmock_response(manticore_response)
-
webmock_response = WebMock::Response.new
-
webmock_response.status = [manticore_response.code, manticore_response.message]
-
webmock_response.body = manticore_response.body
-
webmock_response.headers = manticore_response.headers
-
webmock_response
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'net/http'
-
1
require 'net/https'
-
1
require 'stringio'
-
1
require File.join(File.dirname(__FILE__), 'net_http_response')
-
-
-
1
module WebMock
-
1
module HttpLibAdapters
-
1
class NetHttpAdapter < HttpLibAdapter
-
1
adapter_for :net_http
-
-
1
OriginalNetHTTP = Net::HTTP unless const_defined?(:OriginalNetHTTP)
-
1
OriginalNetBufferedIO = Net::BufferedIO unless const_defined?(:OriginalNetBufferedIO)
-
-
1
def self.enable!
-
1
Net.send(:remove_const, :BufferedIO)
-
1
Net.send(:remove_const, :HTTP)
-
1
Net.send(:remove_const, :HTTPSession)
-
1
Net.send(:const_set, :HTTP, @webMockNetHTTP)
-
1
Net.send(:const_set, :HTTPSession, @webMockNetHTTP)
-
1
Net.send(:const_set, :BufferedIO, Net::WebMockNetBufferedIO)
-
end
-
-
1
def self.disable!
-
1
Net.send(:remove_const, :BufferedIO)
-
1
Net.send(:remove_const, :HTTP)
-
1
Net.send(:remove_const, :HTTPSession)
-
1
Net.send(:const_set, :HTTP, OriginalNetHTTP)
-
1
Net.send(:const_set, :HTTPSession, OriginalNetHTTP)
-
1
Net.send(:const_set, :BufferedIO, OriginalNetBufferedIO)
-
-
#copy all constants from @webMockNetHTTP to original Net::HTTP
-
#in case any constants were added to @webMockNetHTTP instead of Net::HTTP
-
#after WebMock was enabled.
-
#i.e Net::HTTP::DigestAuth
-
1
@webMockNetHTTP.constants.each do |constant|
-
23
if !OriginalNetHTTP.constants.map(&:to_s).include?(constant.to_s)
-
OriginalNetHTTP.send(:const_set, constant, @webMockNetHTTP.const_get(constant))
-
end
-
end
-
end
-
-
1
@webMockNetHTTP = Class.new(Net::HTTP) do
-
1
class << self
-
1
def socket_type
-
4
StubSocket
-
end
-
-
1
if Module.method(:const_defined?).arity == 1
-
def const_defined?(name)
-
super || self.superclass.const_defined?(name)
-
end
-
else
-
1
def const_defined?(name, inherit=true)
-
super || self.superclass.const_defined?(name, inherit)
-
end
-
end
-
-
1
if Module.method(:const_get).arity != 1
-
1
def const_get(name, inherit=true)
-
super
-
rescue NameError
-
self.superclass.const_get(name, inherit)
-
end
-
end
-
-
1
if Module.method(:constants).arity != 0
-
1
def constants(inherit=true)
-
1
(super + self.superclass.constants(inherit)).uniq
-
end
-
end
-
end
-
-
1
def request(request, body = nil, &block)
-
4
request_signature = WebMock::NetHTTPUtility.request_signature_from_request(self, request, body)
-
-
4
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
-
-
4
if webmock_response = WebMock::StubRegistry.instance.response_for_request(request_signature)
-
4
@socket = Net::HTTP.socket_type.new
-
4
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :net_http}, request_signature, webmock_response)
-
4
build_net_http_response(webmock_response, &block)
-
elsif WebMock.net_connect_allowed?(request_signature.uri)
-
check_right_http_connection
-
after_request = lambda do |response|
-
if WebMock::CallbackRegistry.any_callbacks?
-
webmock_response = build_webmock_response(response)
-
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :net_http, :real_request => true}, request_signature, webmock_response)
-
end
-
response.extend Net::WebMockHTTPResponse
-
block.call response if block
-
response
-
end
-
super_with_after_request = lambda {
-
response = super(request, nil, &nil)
-
after_request.call(response)
-
}
-
if started?
-
if WebMock::Config.instance.net_http_connect_on_start
-
super_with_after_request.call
-
else
-
start_with_connect_without_finish {
-
super_with_after_request.call
-
}
-
end
-
else
-
start_with_connect {
-
super_with_after_request.call
-
}
-
end
-
else
-
raise WebMock::NetConnectNotAllowedError.new(request_signature)
-
end
-
end
-
-
1
def start_without_connect
-
raise IOError, 'HTTP session already opened' if @started
-
if block_given?
-
begin
-
@started = true
-
return yield(self)
-
ensure
-
do_finish
-
end
-
end
-
@started = true
-
self
-
end
-
-
-
1
def start_with_connect_without_finish # :yield: http
-
if block_given?
-
begin
-
do_start
-
return yield(self)
-
end
-
end
-
do_start
-
self
-
end
-
-
1
alias_method :start_with_connect, :start
-
-
1
def start(&block)
-
if WebMock::Config.instance.net_http_connect_on_start
-
super(&block)
-
else
-
start_without_connect(&block)
-
end
-
end
-
-
1
def build_net_http_response(webmock_response, &block)
-
4
response = Net::HTTPResponse.send(:response_class, webmock_response.status[0].to_s).new("1.0", webmock_response.status[0].to_s, webmock_response.status[1])
-
4
body = webmock_response.body
-
4
body = nil if body.to_s == ''
-
-
4
response.instance_variable_set(:@body, body)
-
4
webmock_response.headers.to_a.each do |name, values|
-
19
values = [values] unless values.is_a?(Array)
-
19
values.each do |value|
-
19
response.add_field(name, value)
-
end
-
end
-
-
4
response.instance_variable_set(:@read, true)
-
-
4
response.extend Net::WebMockHTTPResponse
-
-
4
raise Timeout::Error, "execution expired" if webmock_response.should_timeout
-
-
4
webmock_response.raise_error_if_any
-
-
4
yield response if block_given?
-
-
4
response
-
end
-
-
1
def build_webmock_response(net_http_response)
-
webmock_response = WebMock::Response.new
-
webmock_response.status = [
-
net_http_response.code.to_i,
-
net_http_response.message]
-
webmock_response.headers = net_http_response.to_hash
-
webmock_response.body = net_http_response.body
-
webmock_response
-
end
-
-
-
1
def check_right_http_connection
-
unless @@alredy_checked_for_right_http_connection ||= false
-
WebMock::NetHTTPUtility.puts_warning_for_right_http_if_needed
-
@@alredy_checked_for_right_http_connection = true
-
end
-
end
-
end
-
1
@webMockNetHTTP.version_1_2
-
[
-
[:Get, Net::HTTP::Get],
-
[:Post, Net::HTTP::Post],
-
[:Put, Net::HTTP::Put],
-
[:Delete, Net::HTTP::Delete],
-
[:Head, Net::HTTP::Head],
-
[:Options, Net::HTTP::Options]
-
1
].each do |c|
-
6
@webMockNetHTTP.const_set(c[0], c[1])
-
end
-
end
-
end
-
end
-
-
# patch for StringIO behavior in Ruby 2.2.3
-
# https://github.com/bblimke/webmock/issues/558
-
1
class PatchedStringIO < StringIO #:nodoc:
-
-
1
alias_method :orig_read_nonblock, :read_nonblock
-
-
1
def read_nonblock(size, *args)
-
orig_read_nonblock(size)
-
end
-
-
end
-
-
1
class StubSocket #:nodoc:
-
-
1
attr_accessor :read_timeout, :continue_timeout
-
-
1
def initialize(*args)
-
end
-
-
1
def closed?
-
@closed ||= true
-
end
-
-
1
def readuntil(*args)
-
end
-
-
end
-
-
1
module Net #:nodoc: all
-
-
1
class WebMockNetBufferedIO < BufferedIO
-
1
def initialize(io, debug_output = nil)
-
@read_timeout = 60
-
@rbuf = ''
-
@debug_output = debug_output
-
-
@io = case io
-
when Socket, OpenSSL::SSL::SSLSocket, IO
-
io
-
when StringIO
-
PatchedStringIO.new(io.string)
-
when String
-
PatchedStringIO.new(io)
-
end
-
raise "Unable to create local socket" unless @io
-
end
-
end
-
-
end
-
-
-
1
module WebMock
-
1
module NetHTTPUtility
-
-
1
def self.request_signature_from_request(net_http, request, body = nil)
-
4
protocol = net_http.use_ssl? ? "https" : "http"
-
-
4
path = request.path
-
-
4
if path.respond_to?(:request_uri) #https://github.com/bblimke/webmock/issues/288
-
path = path.request_uri
-
end
-
-
4
path = WebMock::Util::URI.heuristic_parse(path).request_uri if path =~ /^http/
-
-
4
if request["authorization"] =~ /^Basic /
-
userinfo = WebMock::Util::Headers.decode_userinfo_from_header(request["authorization"])
-
userinfo = WebMock::Util::URI.encode_unsafe_chars_in_userinfo(userinfo) + "@"
-
else
-
4
userinfo = ""
-
end
-
-
4
uri = "#{protocol}://#{userinfo}#{net_http.address}:#{net_http.port}#{path}"
-
4
method = request.method.downcase.to_sym
-
-
28
headers = Hash[*request.to_hash.map {|k,v| [k, v]}.inject([]) {|r,x| r + x}]
-
4
validate_headers(headers)
-
16
headers.reject! {|k,v| k =~ /[Aa]uthorization/ && v.first =~ /^Basic / } #we added it to url userinfo
-
-
4
if request.body_stream
-
body = request.body_stream.read
-
request.body_stream = nil
-
end
-
-
4
if body != nil && body.respond_to?(:read)
-
request.set_body_internal body.read
-
else
-
4
request.set_body_internal body
-
end
-
-
4
WebMock::RequestSignature.new(method, uri, :body => request.body, :headers => headers)
-
end
-
-
1
def self.validate_headers(headers)
-
# If you make a request with headers that are symbols Net::HTTP raises a NoMethodError
-
#
-
# WebMock normalizes headers when creating a RequestSignature,
-
# and will update all headers from symbols to strings.
-
#
-
# This could create a false positive in a test suite with WebMock.
-
#
-
# So before this point, WebMock raises an ArgumentError if any of the headers are symbols
-
# instead of the cryptic NoMethodError "undefined method `split' ...` from Net::HTTP
-
16
header_as_symbol = headers.keys.find {|header| header.is_a? Symbol}
-
4
if header_as_symbol
-
raise ArgumentError.new("Net:HTTP does not accept headers as symbols")
-
end
-
end
-
-
1
def self.check_right_http_connection
-
1
@was_right_http_connection_loaded = defined?(RightHttpConnection)
-
end
-
-
1
def self.puts_warning_for_right_http_if_needed
-
if !@was_right_http_connection_loaded && defined?(RightHttpConnection)
-
$stderr.puts "\nWarning: RightHttpConnection has to be required before WebMock is required !!!\n"
-
end
-
end
-
-
end
-
end
-
-
1
WebMock::NetHTTPUtility.check_right_http_connection
-
# This code is entierly copied from VCR (http://github.com/myronmarston/vcr) by courtesy of Myron Marston
-
-
# A Net::HTTP response that has already been read raises an IOError when #read_body
-
# is called with a destination string or block.
-
#
-
# This causes a problem when VCR records a response--it reads the body before yielding
-
# the response, and if the code that is consuming the HTTP requests uses #read_body, it
-
# can cause an error.
-
#
-
# This is a bit of a hack, but it allows a Net::HTTP response to be "re-read"
-
# after it has aleady been read. This attemps to preserve the behavior of
-
# #read_body, acting just as if it had never been read.
-
-
-
1
module Net
-
1
module WebMockHTTPResponse
-
1
def read_body(dest = nil, &block)
-
4
if !(defined?(@__read_body_previously_called).nil?) && @__read_body_previously_called
-
return super
-
end
-
4
return @body if dest.nil? && block.nil?
-
raise ArgumentError.new("both arg and block given for HTTP method") if dest && block
-
return nil if @body.nil?
-
-
dest ||= ::Net::ReadAdapter.new(block)
-
dest << @body
-
@body = dest
-
ensure
-
# allow subsequent calls to #read_body to proceed as normal, without our hack...
-
4
@__read_body_previously_called = true
-
end
-
end
-
end
-
-
1
begin
-
1
require 'patron'
-
rescue LoadError
-
# patron not found
-
end
-
-
1
if defined?(::Patron)
-
module WebMock
-
module HttpLibAdapters
-
class PatronAdapter < ::WebMock::HttpLibAdapter
-
adapter_for :patron
-
-
OriginalPatronSession = ::Patron::Session unless const_defined?(:OriginalPatronSession)
-
-
class WebMockPatronSession < ::Patron::Session
-
def handle_request(req)
-
request_signature =
-
WebMock::HttpLibAdapters::PatronAdapter.build_request_signature(req)
-
-
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
-
-
if webmock_response = WebMock::StubRegistry.instance.response_for_request(request_signature)
-
WebMock::HttpLibAdapters::PatronAdapter.
-
handle_file_name(req, webmock_response)
-
res = WebMock::HttpLibAdapters::PatronAdapter.
-
build_patron_response(webmock_response, default_response_charset)
-
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :patron}, request_signature, webmock_response)
-
res
-
elsif WebMock.net_connect_allowed?(request_signature.uri)
-
res = super
-
if WebMock::CallbackRegistry.any_callbacks?
-
webmock_response = WebMock::HttpLibAdapters::PatronAdapter.
-
build_webmock_response(res)
-
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :patron, :real_request => true}, request_signature,
-
webmock_response)
-
end
-
res
-
else
-
raise WebMock::NetConnectNotAllowedError.new(request_signature)
-
end
-
end
-
end
-
-
def self.enable!
-
Patron.send(:remove_const, :Session)
-
Patron.send(:const_set, :Session, WebMockPatronSession)
-
end
-
-
def self.disable!
-
Patron.send(:remove_const, :Session)
-
Patron.send(:const_set, :Session, OriginalPatronSession)
-
end
-
-
def self.handle_file_name(req, webmock_response)
-
if req.action == :get && req.file_name
-
begin
-
File.open(req.file_name, "w") do |f|
-
f.write webmock_response.body
-
end
-
rescue Errno::EACCES
-
raise ArgumentError.new("Unable to open specified file.")
-
end
-
end
-
end
-
-
def self.build_request_signature(req)
-
uri = WebMock::Util::URI.heuristic_parse(req.url)
-
uri.path = uri.normalized_path.gsub("[^:]//","/")
-
uri.user = req.username
-
uri.password = req.password
-
-
if [:put, :post].include?(req.action)
-
if req.file_name
-
if !File.exist?(req.file_name) || !File.readable?(req.file_name)
-
raise ArgumentError.new("Unable to open specified file.")
-
end
-
request_body = File.read(req.file_name)
-
elsif req.upload_data
-
request_body = req.upload_data
-
else
-
raise ArgumentError.new("Must provide either data or a filename when doing a PUT or POST")
-
end
-
end
-
-
request_signature = WebMock::RequestSignature.new(
-
req.action,
-
uri.to_s,
-
:body => request_body,
-
:headers => req.headers
-
)
-
request_signature
-
end
-
-
def self.build_patron_response(webmock_response, default_response_charset)
-
raise ::Patron::TimeoutError if webmock_response.should_timeout
-
webmock_response.raise_error_if_any
-
-
header_fields = (webmock_response.headers || []).map { |(k, vs)| Array(vs).map { |v| "#{k}: #{v}" } }.flatten
-
status_line = "HTTP/1.1 #{webmock_response.status[0]} #{webmock_response.status[1]}"
-
header_data = ([status_line] + header_fields).join("\r\n")
-
-
::Patron::Response.new(
-
"",
-
webmock_response.status[0],
-
0,
-
header_data,
-
webmock_response.body,
-
default_response_charset
-
)
-
end
-
-
def self.build_webmock_response(patron_response)
-
webmock_response = WebMock::Response.new
-
reason = patron_response.status_line.
-
scan(%r(\AHTTP/(\d+\.\d+)\s+(\d\d\d)\s*([^\r\n]+)?))[0][2]
-
webmock_response.status = [patron_response.status, reason]
-
webmock_response.body = patron_response.body
-
webmock_response.headers = patron_response.headers
-
webmock_response
-
end
-
end
-
end
-
end
-
end
-
1
begin
-
1
require 'typhoeus'
-
rescue LoadError
-
# typhoeus not found
-
end
-
-
1
if defined?(Typhoeus)
-
WebMock::VersionChecker.new('Typhoeus', Typhoeus::VERSION, '0.3.2').check_version!
-
-
module WebMock
-
module HttpLibAdapters
-
class TyphoeusAdapter < HttpLibAdapter
-
adapter_for :typhoeus
-
-
def self.enable!
-
@disabled = false
-
add_before_callback
-
add_after_request_callback
-
::Typhoeus::Config.block_connection = true
-
end
-
-
def self.disable!
-
@disabled = true
-
remove_after_request_callback
-
remove_before_callback
-
::Typhoeus::Config.block_connection = false
-
end
-
-
def self.disabled?
-
!!@disabled
-
end
-
-
def self.add_before_callback
-
unless Typhoeus.before.include?(BEFORE_CALLBACK)
-
Typhoeus.before << BEFORE_CALLBACK
-
end
-
end
-
-
def self.remove_before_callback
-
Typhoeus.before.delete_if {|v| v == BEFORE_CALLBACK }
-
end
-
-
def self.add_after_request_callback
-
unless Typhoeus.on_complete.include?(AFTER_REQUEST_CALLBACK)
-
Typhoeus.on_complete << AFTER_REQUEST_CALLBACK
-
end
-
end
-
-
def self.remove_after_request_callback
-
Typhoeus.on_complete.delete_if {|v| v == AFTER_REQUEST_CALLBACK }
-
end
-
-
def self.build_request_signature(req)
-
uri = WebMock::Util::URI.heuristic_parse(req.url)
-
uri.path = uri.normalized_path.gsub("[^:]//","/")
-
if req.options[:userpwd]
-
uri.user, uri.password = req.options[:userpwd].split(':')
-
end
-
-
body = req.options[:body]
-
-
if body.is_a?(Hash)
-
body = WebMock::Util::QueryMapper.values_to_query(body)
-
end
-
-
request_signature = WebMock::RequestSignature.new(
-
req.options[:method] || :get,
-
uri.to_s,
-
:body => body,
-
:headers => req.options[:headers]
-
)
-
-
req.instance_variable_set(:@__webmock_request_signature, request_signature)
-
-
request_signature
-
end
-
-
def self.build_webmock_response(typhoeus_response)
-
webmock_response = WebMock::Response.new
-
webmock_response.status = [typhoeus_response.code, typhoeus_response.status_message]
-
webmock_response.body = typhoeus_response.body
-
webmock_response.headers = typhoeus_response.headers
-
webmock_response
-
end
-
-
def self.generate_typhoeus_response(request_signature, webmock_response)
-
response = if webmock_response.should_timeout
-
::Typhoeus::Response.new(
-
:code => 0,
-
:status_message => "",
-
:body => "",
-
:headers => {},
-
:return_code => :operation_timedout
-
)
-
else
-
::Typhoeus::Response.new(
-
:code => webmock_response.status[0],
-
:status_message => webmock_response.status[1],
-
:body => webmock_response.body,
-
:headers => webmock_response.headers,
-
:effective_url => request_signature.uri
-
)
-
end
-
response.mock = :webmock
-
response
-
end
-
-
def self.request_hash(request_signature)
-
hash = {}
-
-
hash[:body] = request_signature.body
-
hash[:headers] = request_signature.headers
-
-
hash
-
end
-
-
AFTER_REQUEST_CALLBACK = Proc.new do |response|
-
request = response.request
-
request_signature = request.instance_variable_get(:@__webmock_request_signature)
-
webmock_response =
-
::WebMock::HttpLibAdapters::TyphoeusAdapter.
-
build_webmock_response(response)
-
if response.mock
-
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :typhoeus},
-
request_signature,
-
webmock_response
-
)
-
else
-
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :typhoeus, :real_request => true},
-
request_signature,
-
webmock_response
-
)
-
end
-
end
-
-
BEFORE_CALLBACK = Proc.new do |request|
-
Typhoeus::Expectation.all.delete_if {|e| e.from == :webmock }
-
res = true
-
-
unless WebMock::HttpLibAdapters::TyphoeusAdapter.disabled?
-
request_signature = ::WebMock::HttpLibAdapters::TyphoeusAdapter.build_request_signature(request)
-
request.block_connection = false;
-
-
::WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
-
-
if webmock_response = ::WebMock::StubRegistry.instance.response_for_request(request_signature)
-
# ::WebMock::HttpLibAdapters::TyphoeusAdapter.stub_typhoeus(request_signature, webmock_response, self)
-
response = ::WebMock::HttpLibAdapters::TyphoeusAdapter.generate_typhoeus_response(request_signature, webmock_response)
-
if request.respond_to?(:on_headers)
-
request.execute_headers_callbacks(response)
-
end
-
if request.respond_to?(:streaming?) && request.streaming?
-
response.options[:response_body] = ""
-
request.on_body.each { |callback| callback.call(webmock_response.body, response) }
-
end
-
request.finish(response)
-
webmock_response.raise_error_if_any
-
res = false
-
elsif !WebMock.net_connect_allowed?(request_signature.uri)
-
raise WebMock::NetConnectNotAllowedError.new(request_signature)
-
end
-
end
-
res
-
end
-
end
-
end
-
end
-
end
-
1
module WebMock
-
1
module Matchers
-
#this is a based on RSpec::Mocks::ArgumentMatchers::HashIncludingMatcher
-
#https://github.com/rspec/rspec-mocks/blob/master/lib/rspec/mocks/argument_matchers.rb
-
1
class HashIncludingMatcher
-
1
def initialize(expected)
-
@expected = Hash[WebMock::Util::HashKeysStringifier.stringify_keys!(expected, :deep => true).sort]
-
end
-
-
1
def ==(actual)
-
@expected.all? {|k,v| actual.has_key?(k) && v === actual[k]}
-
rescue NoMethodError
-
false
-
end
-
-
1
def inspect
-
"hash_including(#{@expected.inspect})"
-
end
-
-
1
def self.from_rspec_matcher(matcher)
-
new(matcher.instance_variable_get(:@expected))
-
end
-
end
-
-
#this is a based on RSpec::Mocks::ArgumentMatchers::AnyArgMatcher
-
1
class AnyArgMatcher
-
1
def initialize(ignore)
-
end
-
-
1
def ==(other)
-
true
-
end
-
end
-
-
end
-
end
-
1
module WebMock
-
1
class RackResponse < Response
-
1
def initialize(app)
-
@app = app
-
end
-
-
1
def evaluate(request)
-
env = build_rack_env(request)
-
-
status, headers, response = @app.call(env)
-
-
Response.new(
-
:body => body_from_rack_response(response),
-
:headers => headers,
-
:status => status
-
)
-
end
-
-
1
def body_from_rack_response(response)
-
body = ""
-
response.each { |line| body << line }
-
response.close if response.respond_to?(:close)
-
return body
-
end
-
-
1
def build_rack_env(request)
-
uri = request.uri
-
headers = (request.headers || {}).dup
-
body = request.body || ''
-
-
env = {
-
# CGI variables specified by Rack
-
'REQUEST_METHOD' => request.method.to_s.upcase,
-
'CONTENT_TYPE' => headers.delete('Content-Type'),
-
'CONTENT_LENGTH' => body.bytesize,
-
'PATH_INFO' => uri.path,
-
'QUERY_STRING' => uri.query || '',
-
'SERVER_NAME' => uri.host,
-
'SERVER_PORT' => uri.port,
-
'SCRIPT_NAME' => ""
-
}
-
-
env['HTTP_AUTHORIZATION'] = 'Basic ' + [uri.userinfo].pack('m').delete("\r\n") if uri.userinfo
-
-
# Rack-specific variables
-
env['rack.input'] = StringIO.new(body)
-
env['rack.errors'] = $stderr
-
env['rack.version'] = Rack::VERSION
-
env['rack.url_scheme'] = uri.scheme
-
env['rack.run_once'] = true
-
env['rack.session'] = session
-
env['rack.session.options'] = session_options
-
-
headers.each do |k, v|
-
env["HTTP_#{k.tr('-','_').upcase}"] = v
-
end
-
-
env
-
end
-
-
1
def session
-
@session ||= {}
-
end
-
-
1
def session_options
-
@session_options ||= {}
-
end
-
end
-
end
-
1
require "hashdiff"
-
1
require "json"
-
-
1
module WebMock
-
1
class RequestBodyDiff
-
-
1
def initialize(request_signature, request_stub)
-
@request_signature = request_signature
-
@request_stub = request_stub
-
end
-
-
1
def body_diff
-
return {} unless request_signature_diffable? && request_stub_diffable?
-
-
HashDiff.diff(request_signature_body_hash, request_stub_body_hash)
-
end
-
-
1
private
-
-
1
attr_reader :request_signature, :request_stub
-
-
1
def request_signature_diffable?
-
request_signature.json_headers? && request_signature_parseable_json?
-
end
-
-
1
def request_stub_diffable?
-
request_stub_body.is_a?(Hash) || request_stub_parseable_json?
-
end
-
-
1
def request_signature_body_hash
-
JSON.parse(request_signature.body)
-
end
-
-
1
def request_stub_body_hash
-
return request_stub_body if request_stub_body.is_a?(Hash)
-
-
JSON.parse(request_stub_body)
-
end
-
-
1
def request_stub_body
-
request_stub.request_pattern &&
-
request_stub.request_pattern.body_pattern &&
-
request_stub.request_pattern.body_pattern.pattern
-
end
-
-
1
def request_signature_parseable_json?
-
parseable_json?(request_signature.body)
-
end
-
-
1
def request_stub_parseable_json?
-
parseable_json?(request_stub_body)
-
end
-
-
1
def parseable_json?(body_pattern)
-
return false unless body_pattern.is_a?(String)
-
-
JSON.parse(body_pattern)
-
true
-
rescue JSON::ParserError
-
false
-
end
-
end
-
end
-
1
module WebMock
-
1
class RequestExecutionVerifier
-
-
1
attr_accessor :request_pattern, :expected_times_executed, :times_executed, :at_least_times_executed, :at_most_times_executed
-
-
1
def initialize(request_pattern = nil, expected_times_executed = nil, at_least_times_executed = nil, at_most_times_executed = nil)
-
@request_pattern = request_pattern
-
@expected_times_executed = expected_times_executed
-
@at_least_times_executed = at_least_times_executed
-
@at_most_times_executed = at_most_times_executed
-
end
-
-
1
def matches?
-
@times_executed =
-
RequestRegistry.instance.times_executed(@request_pattern)
-
-
if @at_least_times_executed
-
@times_executed >= @at_least_times_executed
-
elsif @at_most_times_executed
-
@times_executed <= @at_most_times_executed
-
else
-
@times_executed == (@expected_times_executed || 1)
-
end
-
end
-
-
1
def does_not_match?
-
@times_executed =
-
RequestRegistry.instance.times_executed(@request_pattern)
-
if @expected_times_executed
-
@times_executed != @expected_times_executed
-
else
-
@times_executed == 0
-
end
-
end
-
-
1
def description
-
"request #{request_pattern} #{quantity_phrase.strip}"
-
end
-
-
1
def failure_message
-
failure_message_phrase(false)
-
end
-
-
1
def failure_message_when_negated
-
failure_message_phrase(true)
-
end
-
-
1
def self.executed_requests_message
-
"\n\nThe following requests were made:\n\n#{RequestRegistry.instance}\n" + "="*60
-
end
-
-
1
private
-
-
1
def failure_message_phrase(is_negated=false)
-
negation = is_negated ? "was not" : "was"
-
text = "The request #{request_pattern} #{negation} expected to execute #{quantity_phrase(is_negated)}but it executed #{times(times_executed)}"
-
text << self.class.executed_requests_message
-
text
-
end
-
-
1
def quantity_phrase(is_negated=false)
-
if @at_least_times_executed
-
"at least #{times(@at_least_times_executed)} "
-
elsif @at_most_times_executed
-
"at most #{times(@at_most_times_executed)} "
-
elsif @expected_times_executed
-
"#{times(@expected_times_executed)} "
-
else
-
is_negated ? "" : "#{times(1)} "
-
end
-
end
-
-
1
def times(times)
-
"#{times} time#{ (times == 1) ? '' : 's'}"
-
end
-
-
end
-
end
-
1
module WebMock
-
-
1
module RSpecMatcherDetector
-
1
def rSpecHashIncludingMatcher?(matcher)
-
matcher.class.name =~ /R?Spec::Mocks::ArgumentMatchers::HashIncludingMatcher/
-
end
-
end
-
-
1
class RequestPattern
-
-
1
attr_reader :method_pattern, :uri_pattern, :body_pattern, :headers_pattern
-
-
1
def initialize(method, uri, options = {})
-
1
@method_pattern = MethodPattern.new(method)
-
1
@uri_pattern = create_uri_pattern(uri)
-
1
@body_pattern = nil
-
1
@headers_pattern = nil
-
1
@with_block = nil
-
1
assign_options(options)
-
end
-
-
1
def with(options = {}, &block)
-
1
raise ArgumentError.new('#with method invoked with no arguments. Either options hash or block must be specified.') if options.empty? && !block_given?
-
1
assign_options(options)
-
1
@with_block = block
-
1
self
-
end
-
-
1
def matches?(request_signature)
-
8
content_type = request_signature.headers['Content-Type'] if request_signature.headers
-
8
content_type = content_type.split(';').first if content_type
-
@method_pattern.matches?(request_signature.method) &&
-
8
@uri_pattern.matches?(request_signature.uri) &&
-
8
(@body_pattern.nil? || @body_pattern.matches?(request_signature.body, content_type || "")) &&
-
8
(@headers_pattern.nil? || @headers_pattern.matches?(request_signature.headers)) &&
-
8
(@with_block.nil? || @with_block.call(request_signature))
-
end
-
-
1
def to_s
-
string = "#{@method_pattern.to_s.upcase}"
-
string << " #{@uri_pattern.to_s}"
-
string << " with body #{@body_pattern.to_s}" if @body_pattern
-
string << " with headers #{@headers_pattern.to_s}" if @headers_pattern
-
string << " with given block" if @with_block
-
string
-
end
-
-
1
private
-
-
-
1
def assign_options(options)
-
2
options = WebMock::Util::HashKeysStringifier.stringify_keys!(options, :deep => true)
-
2
HashValidator.new(options).validate_keys('body', 'headers', 'query')
-
2
@body_pattern = BodyPattern.new(options['body']) if options.has_key?('body')
-
2
@headers_pattern = HeadersPattern.new(options['headers']) if options.has_key?('headers')
-
2
@uri_pattern.add_query_params(options['query']) if options.has_key?('query')
-
end
-
-
1
def create_uri_pattern(uri)
-
1
if uri.is_a?(Regexp)
-
1
URIRegexpPattern.new(uri)
-
elsif uri.is_a?(Addressable::Template)
-
URIAddressablePattern.new(uri)
-
else
-
URIStringPattern.new(uri)
-
end
-
end
-
-
end
-
-
-
1
class MethodPattern
-
1
def initialize(pattern)
-
1
@pattern = pattern
-
end
-
-
1
def matches?(method)
-
8
@pattern == method || @pattern == :any
-
end
-
-
1
def to_s
-
@pattern.to_s
-
end
-
end
-
-
-
1
class URIPattern
-
1
include RSpecMatcherDetector
-
-
1
def initialize(pattern)
-
1
@pattern = case pattern
-
when Addressable::URI, Addressable::Template
-
pattern
-
else
-
1
WebMock::Util::URI.normalize_uri(pattern)
-
end
-
1
@query_params = nil
-
end
-
-
1
def add_query_params(query_params)
-
@query_params = if query_params.is_a?(Hash)
-
query_params
-
elsif query_params.is_a?(WebMock::Matchers::HashIncludingMatcher)
-
query_params
-
elsif rSpecHashIncludingMatcher?(query_params)
-
WebMock::Matchers::HashIncludingMatcher.from_rspec_matcher(query_params)
-
else
-
WebMock::Util::QueryMapper.query_to_values(query_params, :notation => Config.instance.query_values_notation)
-
end
-
end
-
-
1
def to_s
-
str = @pattern.inspect
-
str += " with query params #{@query_params.inspect}" if @query_params
-
str
-
end
-
end
-
-
1
class URIRegexpPattern < URIPattern
-
1
def matches?(uri)
-
8
WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u| u.match(@pattern) } &&
-
8
(@query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query, :notation => Config.instance.query_values_notation))
-
end
-
-
1
def to_s
-
str = @pattern.inspect
-
str += " with query params #{@query_params.inspect}" if @query_params
-
str
-
end
-
end
-
-
1
class URIAddressablePattern < URIPattern
-
1
def matches?(uri)
-
if @query_params.nil?
-
# Let Addressable check the whole URI
-
WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u| @pattern.match(u) }
-
else
-
# WebMock checks the query, Addressable checks everything else
-
WebMock::Util::URI.variations_of_uri_as_strings(uri.omit(:query)).any? { |u| @pattern.match(u) } &&
-
@query_params == WebMock::Util::QueryMapper.query_to_values(uri.query)
-
end
-
end
-
-
1
def add_query_params(query_params)
-
warn "WebMock warning: ignoring query params in RFC 6570 template and checking them with WebMock"
-
super(query_params)
-
end
-
-
1
def to_s
-
str = @pattern.pattern.inspect
-
str += " with variables #{@pattern.variables.inspect}" if @pattern.variables
-
str
-
end
-
end
-
-
1
class URIStringPattern < URIPattern
-
1
def matches?(uri)
-
if @pattern.is_a?(Addressable::URI)
-
if @query_params
-
uri.omit(:query) === @pattern &&
-
(@query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query, :notation => Config.instance.query_values_notation))
-
else
-
uri === @pattern
-
end
-
else
-
false
-
end
-
end
-
-
1
def add_query_params(query_params)
-
super
-
if @query_params.is_a?(Hash) || @query_params.is_a?(String)
-
query_hash = (WebMock::Util::QueryMapper.query_to_values(@pattern.query, :notation => Config.instance.query_values_notation) || {}).merge(@query_params)
-
@pattern.query = WebMock::Util::QueryMapper.values_to_query(query_hash, :notation => WebMock::Config.instance.query_values_notation)
-
@query_params = nil
-
end
-
end
-
-
1
def to_s
-
str = WebMock::Util::URI.strip_default_port_from_uri_string(@pattern.to_s)
-
str += " with query params #{@query_params.inspect}" if @query_params
-
str
-
end
-
end
-
-
-
1
class BodyPattern
-
1
include RSpecMatcherDetector
-
-
1
BODY_FORMATS = {
-
'text/xml' => :xml,
-
'application/xml' => :xml,
-
'application/json' => :json,
-
'text/json' => :json,
-
'application/javascript' => :json,
-
'text/javascript' => :json,
-
'text/html' => :html,
-
'application/x-yaml' => :yaml,
-
'text/yaml' => :yaml,
-
'text/plain' => :plain
-
}
-
-
1
attr_reader :pattern
-
-
1
def initialize(pattern)
-
@pattern = if pattern.is_a?(Hash)
-
normalize_hash(pattern)
-
elsif rSpecHashIncludingMatcher?(pattern)
-
WebMock::Matchers::HashIncludingMatcher.from_rspec_matcher(pattern)
-
else
-
pattern
-
end
-
end
-
-
1
def matches?(body, content_type = "")
-
if (@pattern).is_a?(Hash)
-
return true if @pattern.empty?
-
matching_hashes?(body_as_hash(body, content_type), @pattern)
-
elsif (@pattern).is_a?(WebMock::Matchers::HashIncludingMatcher)
-
@pattern == body_as_hash(body, content_type)
-
else
-
empty_string?(@pattern) && empty_string?(body) ||
-
@pattern == body ||
-
@pattern === body
-
end
-
end
-
-
1
def to_s
-
@pattern.inspect
-
end
-
-
1
private
-
1
def body_as_hash(body, content_type)
-
case BODY_FORMATS[content_type]
-
when :json then
-
WebMock::Util::JSON.parse(body)
-
when :xml then
-
Crack::XML.parse(body)
-
else
-
WebMock::Util::QueryMapper.query_to_values(body, :notation => Config.instance.query_values_notation)
-
end
-
end
-
-
# Compare two hashes for equality
-
#
-
# For two hashes to match they must have the same length and all
-
# values must match when compared using `#===`.
-
#
-
# The following hashes are examples of matches:
-
#
-
# {a: /\d+/} and {a: '123'}
-
#
-
# {a: '123'} and {a: '123'}
-
#
-
# {a: {b: /\d+/}} and {a: {b: '123'}}
-
#
-
# {a: {b: 'wow'}} and {a: {b: 'wow'}}
-
#
-
# @param [Hash] query_parameters typically the result of parsing
-
# JSON, XML or URL encoded parameters.
-
#
-
# @param [Hash] pattern which contains keys with a string, hash or
-
# regular expression value to use for comparison.
-
#
-
# @return [Boolean] true if the paramaters match the comparison
-
# hash, false if not.
-
1
def matching_hashes?(query_parameters, pattern)
-
return false unless query_parameters.is_a?(Hash)
-
return false unless query_parameters.keys.sort == pattern.keys.sort
-
query_parameters.each do |key, actual|
-
expected = pattern[key]
-
-
if actual.is_a?(Hash) && expected.is_a?(Hash)
-
return false unless matching_hashes?(actual, expected)
-
else
-
return false unless expected === actual
-
end
-
end
-
true
-
end
-
-
1
def empty_string?(string)
-
string.nil? || string == ""
-
end
-
-
1
def normalize_hash(hash)
-
Hash[WebMock::Util::HashKeysStringifier.stringify_keys!(hash, :deep => true).sort]
-
end
-
-
end
-
-
1
class HeadersPattern
-
1
def initialize(pattern)
-
@pattern = WebMock::Util::Headers.normalize_headers(pattern) || {}
-
end
-
-
1
def matches?(headers)
-
if empty_headers?(@pattern)
-
empty_headers?(headers)
-
else
-
return false if empty_headers?(headers)
-
@pattern.each do |key, value|
-
return false unless headers.has_key?(key) && value === headers[key]
-
end
-
true
-
end
-
end
-
-
1
def to_s
-
WebMock::Util::Headers.sorted_headers_string(@pattern)
-
end
-
-
1
private
-
-
1
def empty_headers?(headers)
-
headers.nil? || headers == {}
-
end
-
end
-
-
end
-
1
module WebMock
-
-
1
class RequestRegistry
-
1
include Singleton
-
-
1
attr_accessor :requested_signatures
-
-
1
def initialize
-
1
reset!
-
end
-
-
1
def reset!
-
1
self.requested_signatures = Util::HashCounter.new
-
end
-
-
1
def times_executed(request_pattern)
-
self.requested_signatures.hash.select { |request_signature, times_executed|
-
request_pattern.matches?(request_signature)
-
}.inject(0) {|sum, (_, times_executed)| sum + times_executed }
-
end
-
-
1
def to_s
-
if requested_signatures.hash.empty?
-
"No requests were made."
-
else
-
text = ""
-
self.requested_signatures.each do |request_signature, times_executed|
-
text << "#{request_signature} was made #{times_executed} time#{times_executed == 1 ? '' : 's' }\n"
-
end
-
text
-
end
-
end
-
-
end
-
end
-
1
module WebMock
-
-
1
class RequestSignature
-
-
1
attr_accessor :method, :uri, :body
-
1
attr_reader :headers
-
-
1
def initialize(method, uri, options = {})
-
4
self.method = method.to_sym
-
4
self.uri = uri.is_a?(Addressable::URI) ? uri : WebMock::Util::URI.normalize_uri(uri)
-
4
assign_options(options)
-
end
-
-
1
def to_s
-
11
string = "#{self.method.to_s.upcase}"
-
11
string << " #{WebMock::Util::URI.strip_default_port_from_uri_string(self.uri.to_s)}"
-
11
string << " with body '#{body.to_s}'" if body && body.to_s != ''
-
11
if headers && !headers.empty?
-
11
string << " with headers #{WebMock::Util::Headers.sorted_headers_string(headers)}"
-
end
-
11
string
-
end
-
-
1
def headers=(headers)
-
4
@headers = WebMock::Util::Headers.normalize_headers(headers)
-
end
-
-
1
def hash
-
11
self.to_s.hash
-
end
-
-
1
def eql?(other)
-
self.to_s == other.to_s
-
end
-
1
alias == eql?
-
-
1
def url_encoded?
-
!!(headers && headers['Content-Type'] == 'application/x-www-form-urlencoded')
-
end
-
-
1
def json_headers?
-
!!(headers && headers['Content-Type'] == 'application/json')
-
end
-
-
1
private
-
-
1
def assign_options(options)
-
4
self.body = options[:body] if options.has_key?(:body)
-
4
self.headers = options[:headers] if options.has_key?(:headers)
-
end
-
-
end
-
-
end
-
1
require "pp"
-
-
1
module WebMock
-
1
class RequestSignatureSnippet
-
-
1
attr_reader :request_signature, :request_stub
-
-
1
def initialize(request_signature)
-
@request_signature = request_signature
-
@request_stub = RequestStub.from_request_signature(request_signature)
-
end
-
-
1
def stubbing_instructions
-
return unless WebMock.show_stubbing_instructions?
-
-
text = "You can stub this request with the following snippet:\n\n"
-
text << WebMock::StubRequestSnippet.new(request_stub).to_s
-
end
-
-
1
def request_stubs
-
return if WebMock::StubRegistry.instance.request_stubs.empty?
-
-
text = "registered request stubs:\n"
-
WebMock::StubRegistry.instance.request_stubs.each do |stub|
-
text << "\n#{WebMock::StubRequestSnippet.new(stub).to_s(false)}"
-
add_body_diff(stub, text) if WebMock.show_body_diff?
-
end
-
text
-
end
-
-
1
private
-
-
1
def add_body_diff(stub, text)
-
body_diff_str = signature_stub_body_diff(stub)
-
text << "\n\n#{body_diff_str}" unless body_diff_str.empty?
-
end
-
-
1
def signature_stub_body_diff(stub)
-
diff = RequestBodyDiff.new(request_signature, stub).body_diff
-
diff.empty? ? "" : "Body diff:\n #{pretty_print_to_string(diff)}"
-
end
-
-
1
def request_params
-
@request_params ||=
-
if request_signature.json_headers?
-
JSON.parse(request_signature.body)
-
else
-
""
-
end
-
end
-
-
1
def pretty_print_to_string(string_to_print)
-
StringIO.open("") do |stream|
-
PP.pp(string_to_print, stream)
-
stream.rewind
-
stream.read
-
end
-
end
-
-
end
-
end
-
1
module WebMock
-
1
class RequestStub
-
-
1
attr_accessor :request_pattern
-
-
1
def initialize(method, uri)
-
1
@request_pattern = RequestPattern.new(method, uri)
-
1
@responses_sequences = []
-
1
self
-
end
-
-
1
def with(params = {}, &block)
-
1
@request_pattern.with(params, &block)
-
1
self
-
end
-
-
1
def to_return(*response_hashes, &block)
-
1
if block
-
@responses_sequences << ResponsesSequence.new([ResponseFactory.response_for(block)])
-
else
-
2
@responses_sequences << ResponsesSequence.new([*response_hashes].flatten.map {|r| ResponseFactory.response_for(r)})
-
end
-
1
self
-
end
-
1
alias_method :and_return, :to_return
-
-
1
def to_rack(app, options={})
-
@responses_sequences << ResponsesSequence.new([RackResponse.new(app)])
-
end
-
-
1
def to_raise(*exceptions)
-
@responses_sequences << ResponsesSequence.new([*exceptions].flatten.map {|e|
-
ResponseFactory.response_for(:exception => e)
-
})
-
self
-
end
-
1
alias_method :and_raise, :to_raise
-
-
1
def to_timeout
-
@responses_sequences << ResponsesSequence.new([ResponseFactory.response_for(:should_timeout => true)])
-
self
-
end
-
1
alias_method :and_timeout, :to_timeout
-
-
1
def response
-
4
if @responses_sequences.empty?
-
WebMock::Response.new
-
4
elsif @responses_sequences.length > 1
-
@responses_sequences.shift if @responses_sequences.first.end?
-
@responses_sequences.first.next_response
-
else
-
4
@responses_sequences[0].next_response
-
end
-
end
-
-
1
def has_responses?
-
!@responses_sequences.empty?
-
end
-
-
1
def then
-
self
-
end
-
-
1
def times(number)
-
raise "times(N) accepts integers >= 1 only" if !number.is_a?(Fixnum) || number < 1
-
if @responses_sequences.empty?
-
raise "Invalid WebMock stub declaration." +
-
" times(N) can be declared only after response declaration."
-
end
-
@responses_sequences.last.times_to_repeat += number-1
-
self
-
end
-
-
1
def matches?(request_signature)
-
self.request_pattern.matches?(request_signature)
-
end
-
-
1
def to_s
-
self.request_pattern.to_s
-
end
-
-
1
def self.from_request_signature(signature)
-
stub = self.new(signature.method.to_sym, signature.uri.to_s)
-
-
if signature.body.to_s != ''
-
body = if signature.url_encoded?
-
WebMock::Util::QueryMapper.query_to_values(signature.body, :notation => Config.instance.query_values_notation)
-
else
-
signature.body
-
end
-
stub.with(:body => body)
-
end
-
-
if (signature.headers && !signature.headers.empty?)
-
stub.with(:headers => signature.headers)
-
end
-
stub
-
end
-
end
-
end
-
1
require "pathname"
-
-
#compatibility with Ruby 1.9.2 preview1 to allow reading raw responses
-
1
if RUBY_VERSION <= "1.9.2"
-
class StringIO
-
alias_method :read_nonblock, :sysread
-
end
-
end
-
-
1
module WebMock
-
-
1
class ResponseFactory
-
1
def self.response_for(options)
-
1
if options.respond_to?(:call)
-
1
WebMock::DynamicResponse.new(options)
-
else
-
WebMock::Response.new(options)
-
end
-
end
-
end
-
-
1
class Response
-
1
def initialize(options = {})
-
4
if options.is_a?(IO) || options.is_a?(String)
-
self.options = read_raw_response(options)
-
else
-
4
self.options = options
-
end
-
end
-
-
1
def headers
-
8
@headers
-
end
-
-
1
def headers=(headers)
-
4
@headers = headers
-
4
if @headers && !@headers.is_a?(Proc)
-
4
@headers = Util::Headers.normalize_headers(@headers)
-
end
-
end
-
-
1
def body
-
8
@body || ''
-
end
-
-
1
def body=(body)
-
4
@body = body
-
4
assert_valid_body!
-
4
stringify_body!
-
end
-
-
1
def status
-
16
@status || [200, ""]
-
end
-
-
1
def status=(status)
-
4
@status = status.is_a?(Integer) ? [status, ""] : status
-
end
-
-
1
def exception
-
@exception
-
end
-
-
1
def exception=(exception)
-
4
@exception = case exception
-
when String then StandardError.new(exception)
-
when Class then exception.new('Exception from WebMock')
-
when Exception then exception
-
end
-
end
-
-
1
def raise_error_if_any
-
4
raise @exception if @exception
-
end
-
-
1
def should_timeout
-
4
@should_timeout == true
-
end
-
-
1
def options=(options)
-
4
options = WebMock::Util::HashKeysStringifier.stringify_keys!(options)
-
4
HashValidator.new(options).validate_keys('headers', 'status', 'body', 'exception', 'should_timeout')
-
4
self.headers = options['headers']
-
4
self.status = options['status']
-
4
self.body = options['body']
-
4
self.exception = options['exception']
-
4
@should_timeout = options['should_timeout']
-
end
-
-
1
def evaluate(request_signature)
-
self.body = @body.call(request_signature) if @body.is_a?(Proc)
-
self.headers = @headers.call(request_signature) if @headers.is_a?(Proc)
-
self.status = @status.call(request_signature) if @status.is_a?(Proc)
-
@should_timeout = @should_timeout.call(request_signature) if @should_timeout.is_a?(Proc)
-
@exception = @exception.call(request_signature) if @exception.is_a?(Proc)
-
self
-
end
-
-
1
def ==(other)
-
self.body == other.body &&
-
self.headers === other.headers &&
-
self.status == other.status &&
-
self.exception == other.exception &&
-
self.should_timeout == other.should_timeout
-
end
-
-
1
private
-
-
1
def stringify_body!
-
4
if @body.is_a?(IO) || @body.is_a?(Pathname)
-
io = @body
-
@body = io.read
-
io.close if io.respond_to?(:close)
-
end
-
end
-
-
1
def assert_valid_body!
-
4
valid_types = [Proc, IO, Pathname, String, Array]
-
4
return if @body.nil?
-
20
return if valid_types.any? { |c| @body.is_a?(c) }
-
raise InvalidBody, "must be one of: #{valid_types}. '#{@body.class}' given"
-
end
-
-
1
def read_raw_response(raw_response)
-
if raw_response.is_a?(IO)
-
string = raw_response.read
-
raw_response.close
-
raw_response = string
-
end
-
socket = ::Net::BufferedIO.new(raw_response)
-
response = ::Net::HTTPResponse.read_new(socket)
-
transfer_encoding = response.delete('transfer-encoding') #chunks were already read by curl
-
response.reading_body(socket, true) {}
-
-
options = {}
-
options[:headers] = {}
-
response.each_header {|name, value| options[:headers][name] = value}
-
options[:headers]['transfer-encoding'] = transfer_encoding if transfer_encoding
-
options[:body] = response.read_body
-
options[:status] = [response.code.to_i, response.message]
-
options
-
end
-
-
1
InvalidBody = Class.new(StandardError)
-
-
end
-
-
1
class DynamicResponse < Response
-
1
attr_accessor :responder
-
-
1
def initialize(responder)
-
1
@responder = responder
-
end
-
-
1
def evaluate(request_signature)
-
4
options = @responder.call(request_signature)
-
4
Response.new(options)
-
end
-
end
-
end
-
1
module WebMock
-
-
1
class ResponsesSequence
-
-
1
attr_accessor :times_to_repeat
-
-
1
def initialize(responses)
-
1
@times_to_repeat = 1
-
1
@responses = responses
-
1
@current_position = 0
-
end
-
-
1
def end?
-
@times_to_repeat == 0
-
end
-
-
1
def next_response
-
4
if @times_to_repeat > 0
-
1
response = @responses[@current_position]
-
1
increase_position
-
1
response
-
else
-
3
@responses.last
-
end
-
end
-
-
1
private
-
-
1
def increase_position
-
1
if @current_position == (@responses.length - 1)
-
1
@current_position = 0
-
1
@times_to_repeat -= 1
-
else
-
@current_position += 1
-
end
-
end
-
-
end
-
-
end
-
1
module WebMock
-
-
1
class StubRegistry
-
1
include Singleton
-
-
1
attr_accessor :request_stubs
-
-
1
def initialize
-
1
reset!
-
end
-
-
1
def global_stubs
-
9
@global_stubs ||= []
-
end
-
-
1
def reset!
-
1
self.request_stubs = []
-
end
-
-
1
def register_global_stub(&block)
-
# This hash contains the responses returned by the block,
-
# keyed by the exact request (using the object_id).
-
# That way, there's no race condition in case #to_return
-
# doesn't run immediately after stub.with.
-
1
responses = {}
-
-
1
stub = ::WebMock::RequestStub.new(:any, /.*/).with { |request|
-
8
responses[request.object_id] = block.call(request)
-
4
}.to_return(lambda { |request| responses.delete(request.object_id) })
-
-
1
global_stubs.push stub
-
end
-
-
1
def register_request_stub(stub)
-
request_stubs.insert(0, stub)
-
stub
-
end
-
-
1
def remove_request_stub(stub)
-
if not request_stubs.delete(stub)
-
raise "Request stub \n\n #{stub.to_s} \n\n is not registered."
-
end
-
end
-
-
1
def registered_request?(request_signature)
-
4
request_stub_for(request_signature)
-
end
-
-
1
def response_for_request(request_signature)
-
4
stub = request_stub_for(request_signature)
-
4
stub ? evaluate_response_for_request(stub.response, request_signature) : nil
-
end
-
-
1
private
-
-
1
def request_stub_for(request_signature)
-
8
(global_stubs + request_stubs).detect { |registered_request_stub|
-
8
registered_request_stub.request_pattern.matches?(request_signature)
-
}
-
end
-
-
1
def evaluate_response_for_request(response, request_signature)
-
4
response.dup.evaluate(request_signature)
-
end
-
-
end
-
end
-
1
module WebMock
-
1
class StubRequestSnippet
-
1
def initialize(request_stub)
-
@request_stub = request_stub
-
end
-
-
1
def body_pattern
-
request_pattern.body_pattern
-
end
-
-
1
def to_s(with_response = true)
-
request_pattern = @request_stub.request_pattern
-
string = "stub_request(:#{request_pattern.method_pattern.to_s},"
-
string << " \"#{request_pattern.uri_pattern.to_s}\")"
-
-
with = ""
-
-
if (request_pattern.body_pattern)
-
with << ":body => #{request_pattern.body_pattern.to_s}"
-
end
-
-
if (request_pattern.headers_pattern)
-
with << ",\n " unless with.empty?
-
-
with << ":headers => #{request_pattern.headers_pattern.to_s}"
-
end
-
string << ".\n with(#{with})" unless with.empty?
-
if with_response
-
string << ".\n to_return(:status => 200, :body => \"\", :headers => {})"
-
end
-
string
-
end
-
end
-
end
-
1
require 'thread'
-
-
1
module WebMock
-
1
module Util
-
1
class Util::HashCounter
-
1
attr_accessor :hash
-
1
def initialize
-
1
self.hash = {}
-
1
@order = {}
-
1
@max = 0
-
1
@lock = ::Mutex.new
-
end
-
1
def put key, num=1
-
4
@lock.synchronize do
-
4
hash[key] = (hash[key] || 0) + num
-
4
@order[key] = @max = @max + 1
-
end
-
end
-
1
def get key
-
@lock.synchronize do
-
hash[key] || 0
-
end
-
end
-
-
1
def each(&block)
-
@order.to_a.sort {|a, b| a[1] <=> b[1]}.each do |a|
-
block.call(a[0], hash[a[0]])
-
end
-
end
-
end
-
end
-
end
-
1
module WebMock
-
1
module Util
-
1
class HashKeysStringifier
-
-
1
def self.stringify_keys!(arg, options = {})
-
6
case arg
-
when Array
-
arg.map { |elem|
-
options[:deep] ? stringify_keys!(elem, options) : elem
-
}
-
when Hash
-
6
Hash[
-
*arg.map { |key, value|
-
12
k = key.is_a?(Symbol) ? key.to_s : key
-
12
v = (options[:deep] ? stringify_keys!(value, options) : value)
-
12
[k,v]
-
12
}.inject([]) {|r,x| r + x}]
-
else
-
arg
-
end
-
end
-
-
end
-
end
-
end
-
1
module WebMock
-
1
class HashValidator
-
1
def initialize(hash)
-
6
@hash = hash
-
end
-
-
#This code is based on https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/hash/keys.rb
-
1
def validate_keys(*valid_keys)
-
6
valid_keys.flatten!
-
6
@hash.each_key do |k|
-
12
unless valid_keys.include?(k)
-
raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{valid_keys.map(&:inspect).join(', ')}")
-
end
-
end
-
end
-
end
-
end
-
1
module WebMock
-
-
1
module Util
-
-
1
class Headers
-
-
1
def self.normalize_headers(headers)
-
19
return nil unless headers
-
19
array = headers.map { |name, value|
-
166
[name.to_s.split(/_|-/).map { |segment| segment.capitalize }.join("-"),
-
case value
-
when Regexp then value
-
31
when Array then (value.size == 1) ? value.first : value.map {|v| v.to_s}.sort
-
33
else value.to_s
-
end
-
]
-
}
-
83
Hash[*array.inject([]) {|r,x| r + x}]
-
end
-
-
1
def self.sorted_headers_string(headers)
-
11
headers = WebMock::Util::Headers.normalize_headers(headers)
-
11
str = '{'
-
str << headers.map do |k,v|
-
33
v = case v
-
when Regexp then v.inspect
-
when Array then "["+v.map{|w| "'#{w.to_s}'"}.join(", ")+"]"
-
33
else "'#{v.to_s}'"
-
end
-
33
"'#{k}'=>#{v}"
-
11
end.sort.join(", ")
-
11
str << '}'
-
end
-
-
1
def self.decode_userinfo_from_header(header)
-
header.sub(/^Basic /, "").unpack("m").first
-
end
-
-
end
-
-
end
-
-
end
-
# This is a copy of https://github.com/jnunemaker/crack/blob/master/lib/crack/json.rb
-
# with date parsing removed
-
# Copyright (c) 2004-2008 David Heinemeier Hansson
-
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-
1
module WebMock
-
1
module Util
-
1
class JSON
-
1
class ParseError < StandardError; end
-
-
1
def self.parse(json)
-
yaml = unescape(convert_json_to_yaml(json))
-
YAML.load(yaml)
-
rescue ArgumentError => e
-
raise ParseError, "Invalid JSON string: #{yaml}, Error: #{e.inspect}"
-
end
-
-
1
protected
-
1
def self.unescape(str)
-
str.gsub(/\\u([0-9a-f]{4})/) { [$1.hex].pack("U") }
-
end
-
-
# Ensure that ":" and "," are always followed by a space
-
1
def self.convert_json_to_yaml(json) #:nodoc:
-
scanner, quoting, marks, pos, times = StringScanner.new(json), false, [], nil, []
-
while scanner.scan_until(/(\\['"]|['":,\\]|\\.)/)
-
case char = scanner[1]
-
when '"', "'"
-
if !quoting
-
quoting = char
-
pos = scanner.pos
-
elsif quoting == char
-
quoting = false
-
end
-
when ":",","
-
marks << scanner.pos - 1 unless quoting
-
when "\\"
-
scanner.skip(/\\/)
-
end
-
end
-
-
if marks.empty?
-
json.gsub(/\\\//, '/')
-
else
-
left_pos = [-1].push(*marks)
-
right_pos = marks << json.bytesize
-
output = []
-
-
if RUBY_VERSION != "1.9.2"
-
left_pos.each_with_index do |left, i|
-
if json.respond_to?(:byteslice)
-
output << json.byteslice(left.succ..right_pos[i])
-
else
-
output << json[left.succ..right_pos[i]]
-
end
-
end
-
else
-
json_as_binary = json.force_encoding("binary")
-
left_pos.each_with_index do |left, i|
-
output << json_as_binary[left.succ..right_pos[i]]
-
end
-
output.map! do |binary_str|
-
binary_str.force_encoding("UTF-8")
-
end
-
end
-
-
output = output * " "
-
-
times.each { |i| output[i-1] = ' ' }
-
output.gsub!(/\\\//, '/')
-
output
-
end
-
end
-
end
-
end
-
end
-
1
module WebMock::Util
-
1
class QueryMapper
-
1
class << self
-
#This class is based on Addressable::URI pre 2.3.0
-
-
##
-
# Converts the query component to a Hash value.
-
#
-
# @option [Symbol] notation
-
# May be one of <code>:flat</code>, <code>:dot</code>, or
-
# <code>:subscript</code>. The <code>:dot</code> notation is not
-
# supported for assignment. Default value is <code>:subscript</code>.
-
#
-
# @return [Hash, Array] The query string parsed as a Hash or Array object.
-
#
-
# @example
-
# WebMock::Util::QueryMapper.query_to_values("?one=1&two=2&three=3")
-
# #=> {"one" => "1", "two" => "2", "three" => "3"}
-
# WebMock::Util::QueryMapper("?one[two][three]=four").query_values
-
# #=> {"one" => {"two" => {"three" => "four"}}}
-
# WebMock::Util::QueryMapper.query_to_values("?one.two.three=four",
-
# :notation => :dot
-
# )
-
# #=> {"one" => {"two" => {"three" => "four"}}}
-
# WebMock::Util::QueryMapper.query_to_values("?one[two][three]=four",
-
# :notation => :flat
-
# )
-
# #=> {"one[two][three]" => "four"}
-
# WebMock::Util::QueryMapper.query_to_values("?one.two.three=four",
-
# :notation => :flat
-
# )
-
# #=> {"one.two.three" => "four"}
-
# WebMock::Util::QueryMapper(
-
# "?one[two][three][]=four&one[two][three][]=five"
-
# )
-
# #=> {"one" => {"two" => {"three" => ["four", "five"]}}}
-
# WebMock::Util::QueryMapper.query_to_values(
-
# "?one=two&one=three").query_values(:notation => :flat_array)
-
# #=> [['one', 'two'], ['one', 'three']]
-
1
def query_to_values(query, options={})
-
8
return nil if query.nil?
-
8
query.force_encoding('utf-8') if query.respond_to?(:force_encoding)
-
-
8
options[:notation] ||= :subscript
-
-
8
if ![:flat, :dot, :subscript, :flat_array].include?(options[:notation])
-
raise ArgumentError,
-
'Invalid notation. Must be one of: ' +
-
'[:flat, :dot, :subscript, :flat_array].'
-
end
-
-
8
empty_accumulator = :flat_array == options[:notation] ? [] : {}
-
-
8
query_array = collect_query_parts(query)
-
-
8
query_hash = collect_query_hash(query_array, empty_accumulator, options)
-
-
8
normalize_query_hash(query_hash, empty_accumulator, options)
-
end
-
-
1
def normalize_query_hash(query_hash, empty_accumulator, options)
-
8
query_hash.inject(empty_accumulator.dup) do |accumulator, (key, value)|
-
34
if options[:notation] == :flat_array
-
accumulator << [key, value]
-
else
-
34
accumulator[key] = value.kind_of?(Hash) ? dehash(value) : value
-
end
-
34
accumulator
-
end
-
end
-
-
1
def collect_query_parts(query)
-
8
query_parts = query.split('&').map do |pair|
-
34
pair.split('=', 2) if pair && !pair.empty?
-
end
-
8
query_parts.compact
-
end
-
-
1
def collect_query_hash(query_array, empty_accumulator, options)
-
8
query_array.compact.inject(empty_accumulator.dup) do |accumulator, (key, value)|
-
34
value = if value.nil?
-
nil
-
else
-
34
::Addressable::URI.unencode_component(value.gsub(/\+/, ' '))
-
end
-
34
key = Addressable::URI.unencode_component(key)
-
34
key = key.dup.force_encoding(Encoding::ASCII_8BIT) if key.respond_to?(:force_encoding)
-
34
self.__send__("fill_accumulator_for_#{options[:notation]}", accumulator, key, value)
-
34
accumulator
-
end
-
end
-
-
1
def fill_accumulator_for_flat(accumulator, key, value)
-
if accumulator[key]
-
raise ArgumentError, "Key was repeated: #{key.inspect}"
-
end
-
accumulator[key] = value
-
end
-
-
1
def fill_accumulator_for_flat_array(accumulator, key, value)
-
accumulator << [key, value]
-
end
-
-
1
def fill_accumulator_for_dot(accumulator, key, value)
-
array_value = false
-
subkeys = key.split(".")
-
current_hash = accumulator
-
subkeys[0..-2].each do |subkey|
-
current_hash[subkey] = {} unless current_hash[subkey]
-
current_hash = current_hash[subkey]
-
end
-
if array_value
-
if current_hash[subkeys.last] && !current_hash[subkeys.last].is_a?(Array)
-
current_hash[subkeys.last] = [current_hash[subkeys.last]]
-
end
-
current_hash[subkeys.last] = [] unless current_hash[subkeys.last]
-
current_hash[subkeys.last] << value
-
else
-
current_hash[subkeys.last] = value
-
end
-
end
-
-
1
def fill_accumulator_for_subscript(accumulator, key, value)
-
34
current_node = accumulator
-
34
subkeys = key.split(/(?=\[\w)/)
-
34
subkeys[0..-2].each do |subkey|
-
node = subkey =~ /\[\]\z/ ? [] : {}
-
subkey = subkey.gsub(/[\[\]]/, '')
-
if current_node.is_a? Array
-
container = current_node.find { |n| n.is_a?(Hash) && n.has_key?(subkey) }
-
if container
-
current_node = container[subkey]
-
else
-
current_node << {subkey => node}
-
current_node = node
-
end
-
else
-
current_node[subkey] = node unless current_node[subkey]
-
current_node = current_node[subkey]
-
end
-
end
-
34
last_key = subkeys.last
-
34
array_value = !!(last_key =~ /\[\]$/)
-
34
last_key = last_key.gsub(/[\[\]]/, '')
-
34
if current_node.is_a? Array
-
last_container = current_node.select { |n| n.is_a?(Hash) }.last
-
if last_container && !last_container.has_key?(last_key)
-
if array_value
-
last_container[last_key] << value
-
else
-
last_container[last_key] = value
-
end
-
else
-
if array_value
-
current_node << {last_key => [value]}
-
else
-
current_node << {last_key => value}
-
end
-
end
-
else
-
34
if array_value
-
current_node[last_key] = [] unless current_node[last_key]
-
current_node[last_key] << value
-
else
-
34
current_node[last_key] = value
-
end
-
end
-
end
-
-
##
-
# Sets the query component for this URI from a Hash object.
-
# This method produces a query string using the :subscript notation.
-
# An empty Hash will result in a nil query.
-
#
-
# @param [Hash, #to_hash, Array] new_query_values The new query values.
-
1
def values_to_query(new_query_values, options = {})
-
8
options[:notation] ||= :subscript
-
8
return if new_query_values.nil?
-
-
8
unless new_query_values.is_a?(Array)
-
8
unless new_query_values.respond_to?(:to_hash)
-
raise TypeError,
-
"Can't convert #{new_query_values.class} into Hash."
-
end
-
8
new_query_values = new_query_values.to_hash
-
8
new_query_values = new_query_values.inject([]) do |object, (key, value)|
-
34
key = key.to_s if key.is_a?(::Symbol) || key.nil?
-
34
if value.is_a?(Array)
-
value.each { |v| object << [key.to_s + '[]', v] }
-
elsif value.is_a?(Hash)
-
value.each { |k, v| object << ["#{key.to_s}[#{k}]", v]}
-
else
-
34
object << [key.to_s, value]
-
end
-
34
object
-
end
-
# Useful default for OAuth and caching.
-
# Only to be used for non-Array inputs. Arrays should preserve order.
-
8
begin
-
8
new_query_values.sort! # may raise for non-comparable values
-
rescue NoMethodError, ArgumentError
-
# ignore
-
end
-
end
-
-
8
buffer = ''
-
8
new_query_values.each do |parent, value|
-
34
encoded_parent = ::Addressable::URI.encode_component(
-
parent.dup, ::Addressable::URI::CharacterClasses::UNRESERVED
-
)
-
34
buffer << "#{to_query(encoded_parent, value, options)}&"
-
end
-
8
buffer.chop
-
end
-
-
1
def dehash(hash)
-
hash.each do |(key, value)|
-
if value.is_a?(::Hash)
-
hash[key] = self.dehash(value)
-
end
-
end
-
if hash != {} && hash.keys.all? { |key| key =~ /^\d+$/ }
-
hash.sort.inject([]) do |accu, (_, value)|
-
accu << value; accu
-
end
-
else
-
hash
-
end
-
end
-
-
##
-
# Joins and converts parent and value into a properly encoded and
-
# ordered URL query.
-
#
-
# @private
-
# @param [String] parent an URI encoded component.
-
# @param [Array, Hash, Symbol, #to_str] value
-
#
-
# @return [String] a properly escaped and ordered URL query.
-
-
# new_query_values have form [['key1', 'value1'], ['key2', 'value2']]
-
1
def to_query(parent, value, options = {})
-
34
options[:notation] ||= :subscript
-
34
case value
-
when ::Hash
-
value = value.map do |key, val|
-
[
-
::Addressable::URI.encode_component(key.to_s.dup, ::Addressable::URI::CharacterClasses::UNRESERVED),
-
val
-
]
-
end
-
value.sort!
-
buffer = ''
-
value.each do |key, val|
-
new_parent = options[:notation] != :flat_array ? "#{parent}[#{key}]" : parent
-
buffer << "#{to_query(new_parent, val, options)}&"
-
end
-
buffer.chop
-
when ::Array
-
buffer = ''
-
value.each_with_index do |val, i|
-
new_parent = options[:notation] != :flat_array ? "#{parent}[#{i}]" : parent
-
buffer << "#{to_query(new_parent, val, options)}&"
-
end
-
buffer.chop
-
when NilClass
-
parent
-
else
-
34
encoded_value = Addressable::URI.encode_component(
-
value.to_s.dup, Addressable::URI::CharacterClasses::UNRESERVED
-
)
-
34
"#{parent}=#{encoded_value}"
-
end
-
end
-
end
-
-
end
-
end
-
1
module WebMock
-
-
1
module Util
-
-
1
class URI
-
1
module CharacterClasses
-
1
USERINFO = Addressable::URI::CharacterClasses::UNRESERVED + Addressable::URI::CharacterClasses::SUB_DELIMS + "\\:"
-
end
-
-
1
ADDRESSABLE_URIS = Hash.new do |hash, key|
-
8
hash[key] = Addressable::URI.heuristic_parse(key)
-
end
-
-
1
NORMALIZED_URIS = Hash.new do |hash, uri|
-
8
normalized_uri = WebMock::Util::URI.heuristic_parse(uri)
-
8
if normalized_uri.query_values
-
8
sorted_query_values = sort_query_values(WebMock::Util::QueryMapper.query_to_values(normalized_uri.query, :notation => Config.instance.query_values_notation) || {})
-
8
normalized_uri.query = WebMock::Util::QueryMapper.values_to_query(sorted_query_values, :notation => WebMock::Config.instance.query_values_notation)
-
end
-
8
normalized_uri = normalized_uri.normalize #normalize! is slower
-
8
normalized_uri.query = normalized_uri.query.gsub("+", "%2B") if normalized_uri.query
-
8
normalized_uri.port = normalized_uri.inferred_port unless normalized_uri.port
-
8
hash[uri] = normalized_uri
-
end
-
-
1
def self.heuristic_parse(uri)
-
8
ADDRESSABLE_URIS[uri].dup
-
end
-
-
1
def self.normalize_uri(uri)
-
13
return uri if uri.is_a?(Regexp)
-
12
uri = 'http://' + uri unless uri.match('^https?://') if uri.is_a?(String)
-
12
NORMALIZED_URIS[uri].dup
-
end
-
-
1
def self.variations_of_uri_as_strings(uri_object)
-
8
normalized_uri = normalize_uri(uri_object.dup).freeze
-
8
uris = [ normalized_uri ]
-
-
8
if normalized_uri.path == '/'
-
uris = uris_with_trailing_slash_and_without(uris)
-
end
-
-
8
uris = uris_encoded_and_unencoded(uris)
-
-
8
if normalized_uri.port == Addressable::URI.port_mapping[normalized_uri.scheme]
-
8
uris = uris_with_inferred_port_and_without(uris)
-
end
-
-
8
if normalized_uri.scheme == "http"
-
8
uris = uris_with_scheme_and_without(uris)
-
end
-
-
72
uris.map {|uri| uri.to_s.gsub(/^\/\//,'') }.uniq
-
end
-
-
1
def self.strip_default_port_from_uri_string(uri_string)
-
11
case uri_string
-
11
when %r{^http://} then uri_string.sub(%r{:80(/|$)}, '\1')
-
when %r{^https://} then uri_string.sub(%r{:443(/|$)}, '\1')
-
else uri_string
-
end
-
end
-
-
1
def self.encode_unsafe_chars_in_userinfo(userinfo)
-
Addressable::URI.encode_component(userinfo, WebMock::Util::URI::CharacterClasses::USERINFO)
-
end
-
-
1
def self.is_uri_localhost?(uri)
-
uri.is_a?(Addressable::URI) &&
-
%w(localhost 127.0.0.1 0.0.0.0).include?(uri.host)
-
end
-
-
1
private
-
-
1
def self.sort_query_values(query_values)
-
8
sorted_query_values = query_values.sort
-
42
query_values.is_a?(Hash) ? Hash[*sorted_query_values.inject([]) { |values, pair| values + pair}] : sorted_query_values
-
end
-
-
1
def self.uris_with_inferred_port_and_without(uris)
-
uris.map { |uri|
-
16
uri = uri.dup.force_encoding(Encoding::ASCII_8BIT) if uri.respond_to?(:force_encoding)
-
16
[ uri, uri.gsub(%r{(:80)|(:443)}, "").freeze ]
-
8
}.flatten
-
end
-
-
1
def self.uris_encoded_and_unencoded(uris)
-
uris.map do |uri|
-
8
[ uri.to_s, Addressable::URI.unencode(uri, String).freeze ]
-
8
end.flatten
-
end
-
-
1
def self.uris_with_scheme_and_without(uris)
-
uris.map { |uri|
-
32
uri = uri.dup.force_encoding(Encoding::ASCII_8BIT) if uri.respond_to?(:force_encoding)
-
32
[ uri, uri.gsub(%r{^https?://},"").freeze ]
-
8
}.flatten
-
end
-
-
1
def self.uris_with_trailing_slash_and_without(uris)
-
uris = uris.map { |uri|
-
uri = uri.dup.force_encoding(Encoding::ASCII_8BIT) if uri.respond_to?(:force_encoding)
-
[ uri, uri.omit(:path).freeze ]
-
}.flatten
-
end
-
-
end
-
end
-
-
end
-
# This code was created based on https://github.com/myronmarston/vcr/blob/master/lib/vcr/util/version_checker.rb
-
# Thanks to @myronmarston
-
-
# Copyright (c) 2010-2012 Myron Marston
-
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-
1
module WebMock
-
1
class VersionChecker
-
1
def initialize(library_name, library_version, min_patch_level, max_minor_version = nil, unsupported_versions = [])
-
@library_name, @library_version = library_name, library_version
-
@min_patch_level, @max_minor_version = min_patch_level, max_minor_version
-
@unsupported_versions = unsupported_versions || []
-
-
@major, @minor, @patch = parse_version(library_version)
-
@min_major, @min_minor, @min_patch = parse_version(min_patch_level)
-
@max_major, @max_minor = parse_version(max_minor_version) if max_minor_version
-
-
@comparison_result = compare_version
-
end
-
-
1
def check_version!
-
warn_about_too_low if too_low?
-
warn_about_too_high if too_high?
-
warn_about_unsupported_version if unsupported_version?
-
end
-
-
1
private
-
-
1
def too_low?
-
@comparison_result == :too_low
-
end
-
-
1
def too_high?
-
@comparison_result == :too_high
-
end
-
-
1
def unsupported_version?
-
@unsupported_versions.include?(@library_version)
-
end
-
-
1
def warn_about_too_low
-
warn_in_red "You are using #{@library_name} #{@library_version}. " +
-
"WebMock supports version #{version_requirement}."
-
end
-
-
1
def warn_about_too_high
-
warn_in_red "You are using #{@library_name} #{@library_version}. " +
-
"WebMock is known to work with #{@library_name} #{version_requirement}. " +
-
"It may not work with this version."
-
end
-
-
1
def warn_about_unsupported_version
-
warn_in_red "You are using #{@library_name} #{@library_version}. " +
-
"WebMock does not support this version. " +
-
"WebMock supports versions #{version_requirement}."
-
end
-
-
1
def warn_in_red(text)
-
Kernel.warn colorize(text, "\e[31m")
-
end
-
-
1
def compare_version
-
case
-
when @major < @min_major then :too_low
-
when @max_major && @major > @max_major then :too_high
-
when @major > @min_major then :ok
-
when @minor < @min_minor then :too_low
-
when @max_minor && @minor > @max_minor then :too_high
-
when @minor > @min_minor then :ok
-
when @patch < @min_patch then :too_low
-
end
-
end
-
-
1
def version_requirement
-
req = ">= #{@min_patch_level}"
-
req += ", < #{@max_major}.#{@max_minor + 1}" if @max_minor
-
req += ", except versions #{@unsupported_versions.join(',')}" unless @unsupported_versions.empty?
-
req
-
end
-
-
1
def parse_version(version)
-
version.split('.').map { |v| v.to_i }
-
end
-
-
1
def colorize(text, color_code)
-
"#{color_code}#{text}\e[0m"
-
end
-
end
-
end
-
1
module WebMock
-
1
VERSION = '1.24.2' unless defined?(::WebMock::VERSION)
-
end
-
1
module WebMock
-
-
1
def self.included(clazz)
-
WebMock::Deprecation.warning("include WebMock is deprecated. Please include WebMock::API instead")
-
if clazz.instance_methods.map(&:to_s).include?('request')
-
warn "WebMock#request was not included in #{clazz} to avoid name collision"
-
else
-
clazz.class_eval do
-
def request(method, uri)
-
WebMock::Deprecation.warning("WebMock#request is deprecated. Please use WebMock::API#a_request method instead")
-
WebMock.a_request(method, uri)
-
end
-
end
-
end
-
end
-
-
1
include WebMock::API
-
1
extend WebMock::API
-
-
1
class << self
-
1
alias :request :a_request
-
end
-
-
1
def self.version
-
1
VERSION
-
end
-
-
1
def self.disable!(options = {})
-
except = [options[:except]].flatten.compact
-
HttpLibAdapterRegistry.instance.each_adapter do |name, adapter|
-
adapter.enable!
-
adapter.disable! unless except.include?(name)
-
end
-
end
-
-
1
def self.enable!(options = {})
-
1
except = [options[:except]].flatten.compact
-
1
HttpLibAdapterRegistry.instance.each_adapter do |name, adapter|
-
1
adapter.disable!
-
1
adapter.enable! unless except.include?(name)
-
end
-
end
-
-
1
def self.allow_net_connect!(options = {})
-
Config.instance.allow_net_connect = true
-
Config.instance.net_http_connect_on_start = options[:net_http_connect_on_start]
-
end
-
-
1
def self.disable_net_connect!(options = {})
-
Config.instance.allow_net_connect = false
-
Config.instance.allow_localhost = options[:allow_localhost]
-
Config.instance.allow = options[:allow]
-
Config.instance.net_http_connect_on_start = options[:net_http_connect_on_start]
-
end
-
-
1
def self.net_connect_allowed?(uri = nil)
-
if uri.is_a?(String)
-
uri = WebMock::Util::URI.normalize_uri(uri)
-
end
-
-
Config.instance.allow_net_connect ||
-
( Config.instance.allow_localhost && WebMock::Util::URI.is_uri_localhost?(uri) ||
-
Config.instance.allow && net_connect_explicit_allowed?(Config.instance.allow, uri) )
-
end
-
-
1
def self.net_connect_explicit_allowed?(allowed, uri=nil)
-
case allowed
-
when Array
-
allowed.any? { |allowed_item| net_connect_explicit_allowed?(allowed_item, uri) }
-
when Regexp
-
uri.to_s =~ allowed
-
when String
-
allowed == uri.host ||
-
allowed == "#{uri.host}:#{uri.port}"
-
else
-
if allowed.respond_to?(:call)
-
allowed.call(uri)
-
end
-
end
-
end
-
-
1
def self.show_body_diff!
-
Config.instance.show_body_diff = true
-
end
-
-
1
def self.hide_body_diff!
-
Config.instance.show_body_diff = false
-
end
-
-
1
def self.show_body_diff?
-
Config.instance.show_body_diff
-
end
-
-
1
def self.hide_stubbing_instructions!
-
Config.instance.show_stubbing_instructions = false
-
end
-
-
1
def self.show_stubbing_instructions!
-
Config.instance.show_stubbing_instructions = true
-
end
-
-
1
def self.show_stubbing_instructions?
-
Config.instance.show_stubbing_instructions
-
end
-
-
1
def self.reset!
-
WebMock::RequestRegistry.instance.reset!
-
WebMock::StubRegistry.instance.reset!
-
end
-
-
1
def self.reset_webmock
-
WebMock::Deprecation.warning("WebMock.reset_webmock is deprecated. Please use WebMock.reset! method instead")
-
reset!
-
end
-
-
1
def self.reset_callbacks
-
WebMock::CallbackRegistry.reset
-
end
-
-
1
def self.after_request(options={}, &block)
-
2
WebMock::CallbackRegistry.add_callback(options, block)
-
end
-
-
1
def self.registered_request?(request_signature)
-
4
WebMock::StubRegistry.instance.registered_request?(request_signature)
-
end
-
-
1
def self.print_executed_requests
-
puts WebMock::RequestExecutionVerifier.executed_requests_message
-
end
-
-
1
def self.globally_stub_request(&block)
-
1
WebMock::StubRegistry.instance.register_global_stub(&block)
-
end
-
-
%w(
-
allow_net_connect!
-
disable_net_connect!
-
net_connect_allowed?
-
reset_webmock
-
reset_callbacks
-
after_request
-
registered_request?
-
1
).each do |method|
-
7
self.class_eval(%Q(
-
def #{method}(*args, &block)
-
WebMock::Deprecation.warning("WebMock##{method} instance method is deprecated. Please use WebMock.#{method} class method instead")
-
WebMock.#{method}(*args, &block)
-
end
-
))
-
end
-
-
1
self.enable!
-
end
-
1
require 'marvel/version'
-
1
require 'marvel/exceptions'
-
1
require 'httparty'
-
1
require 'marvel/api'
-
-
1
module Marvel
-
1
class << self
-
1
def set_config(config)
-
8
@config = config
-
8
@api = nil
-
end
-
-
1
def total_characters
-
3
response = with_handling do
-
3
api.characters({limit: 1})
-
end
-
1
response['data']['total']
-
end
-
-
1
def sample_character_thumbnail
-
2
response = with_handling do
-
2
api.characters({limit: 1, offset: 1000})
-
end
-
1
character = response['data']['results'].last
-
1
[character['thumbnail']['path'], character['thumbnail']['extension']].join('.')
-
end
-
-
1
def characters_in_comics(comic_ids: [])
-
3
return nil if comic_ids.empty?
-
-
2
response = with_handling do
-
2
api.characters({comics: comic_ids.join(',')})
-
end
-
1
response['data']['results'].map { |character|
-
5
character['name']
-
}
-
end
-
-
1
private
-
-
1
attr_reader :config
-
-
1
def api
-
7
verify_configuration
-
@api ||= Marvel::Api.new(
-
public_key: config[:public_key],
-
private_key: config[:private_key]
-
4
)
-
end
-
-
1
def verify_configuration
-
7
if config.nil?
-
1
raise Marvel::NotConfigured, 'Not configured. Please use #set_config with a hash containing :public_key and :private_key.'
-
end
-
-
6
if !config.has_key?(:public_key) || config[:public_key].empty?
-
1
raise Marvel::NotConfigured, 'No Marvel API public key has been set in the configuration, or is empty.'
-
end
-
-
5
if !config.has_key?(:private_key) || config[:private_key].empty?
-
1
raise Marvel::NotConfigured, 'No Marvel API private key has been set in the configuration, or is empty.'
-
end
-
end
-
-
1
def with_handling
-
7
response = yield
-
4
if response.code != 200
-
1
raise Marvel::ApiError, "Unexpected API response. code=#{response.code} reason=#{response['message']}"
-
end
-
3
response
-
end
-
end
-
end
-
1
module Marvel
-
1
class Api
-
1
include ::HTTParty
-
1
base_uri 'gateway.marvel.com'
-
-
1
def initialize(public_key:, private_key:)
-
4
@public_key = public_key
-
4
@private_key = private_key
-
end
-
-
1
def characters(params={})
-
4
params.merge! auth_params
-
4
options = { query: params }
-
4
self.class.get('/v1/public/characters', options)
-
end
-
-
1
private
-
1
attr_reader :public_key, :private_key
-
-
1
def auth_params
-
4
ts = Time.now.to_i
-
4
hash = Digest::MD5.hexdigest("#{ts}#{private_key}#{public_key}")
-
{
-
ts: ts,
-
apikey: public_key,
-
hash: hash
-
4
}
-
end
-
end
-
end
-
1
module Marvel
-
1
class NotConfigured < StandardError; end
-
1
class ApiError < StandardError; end
-
end
-
1
require 'spec_helper'
-
-
1
describe Marvel do
-
1
it 'has a version number' do
-
1
expect(Marvel::VERSION).not_to be nil
-
end
-
-
1
context 'given a proper configuration' do
-
1
before do
-
4
Marvel.set_config(
-
public_key: 'c94d87eb20347745bbd4d3344530b76c',
-
private_key: '9f6be4b65b1e66b654228e2f22aa822fac88e803'
-
)
-
end
-
-
1
describe '#total_characters' do
-
1
it 'fetches the total number of comic characters' do
-
1
VCR.use_cassette('characters') do
-
1
expect(described_class.total_characters).to eq(1485)
-
end
-
end
-
end
-
-
1
describe '#sample_character_thumbnail' do
-
1
it 'gets a character thumbnail' do
-
1
VCR.use_cassette('sample_thumbnail') do
-
1
expect(described_class.sample_character_thumbnail).to eq('http://i.annihil.us/u/prod/marvel/i/mg/3/00/4c003c66d3393.jpg')
-
end
-
end
-
end
-
-
1
describe '#characters_in_comics' do
-
1
it 'returns the character names for two comics' do
-
1
VCR.use_cassette('characters_filtered_by_comic_ids') do
-
1
character_names = described_class.characters_in_comics(comic_ids: [30090, 162])
-
1
expected_characters = [
-
'Captain America',
-
'Captain Britain',
-
'Iron Man',
-
'Spider-Man',
-
'Thor'
-
]
-
1
expect(character_names).to eq(expected_characters)
-
end
-
end
-
-
1
it 'avoids calling the remote API if not comic ids are specified' do
-
1
character_names = described_class.characters_in_comics(comic_ids: [])
-
1
expect(character_names).to be_nil
-
end
-
end
-
end
-
-
1
context 'without being configured first' do
-
1
describe '#total_characters' do
-
1
it 'raises an exception' do
-
1
Marvel.set_config(nil)
-
1
expect {
-
1
described_class.total_characters
-
}.to raise_exception Marvel::NotConfigured
-
end
-
end
-
-
1
describe '#sample_character_thumbnail' do
-
1
it 'raises an exception' do
-
1
Marvel.set_config({public_key: 'something'})
-
1
expect {
-
1
described_class.sample_character_thumbnail
-
}.to raise_exception Marvel::NotConfigured
-
end
-
end
-
-
1
describe '#characters_in_comics' do
-
1
it 'raises an exception' do
-
1
Marvel.set_config({private_key: 'something'})
-
1
expect {
-
1
described_class.characters_in_comics(comic_ids: [30090, 162])
-
}.to raise_exception Marvel::NotConfigured
-
end
-
end
-
end
-
-
1
context 'with an unsuccessful API response' do
-
1
before do
-
1
Marvel.set_config(
-
public_key: 'foo',
-
private_key: 'bar'
-
)
-
end
-
-
1
describe '#total_characters' do
-
1
it 'encapsulates the error in a known exception' do
-
1
VCR.use_cassette('api_error') do
-
1
expect {
-
1
described_class.total_characters
-
}.to raise_exception(
-
Marvel::ApiError,
-
'Unexpected API response. code=401 reason=The passed API key is invalid.'
-
)
-
end
-
end
-
end
-
end
-
end
-
1
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
-
1
require 'marvel'
-
1
require 'vcr'
-
-
1
VCR.configure do |config|
-
1
config.cassette_library_dir = 'fixtures/vcr_cassettes'
-
1
config.hook_into :webmock
-
1
config.default_cassette_options = {
-
:match_requests_on => [:method,
-
VCR.request_matchers.uri_without_params(:ts, :hash)],
-
}
-
1
config.before_record do |i|
-
i.response.body.scrub
-
end
-
end